JAVA中的线程

本文介绍了Java中线程的基本概念,包括线程的创建(通过继承Thread、实现Runnable和Callable接口)、线程的生命周期、同步锁的使用(同步方法和同步代码块)、死锁的产生和避免,以及线程池的概念和内置线程池的使用。文章通过示例代码详细阐述了线程的操作和管理,并讨论了线程间的通信(wait、notify)以及线程池的优化策略。
摘要由CSDN通过智能技术生成

线程

	线程是什么
    线程的创建方式
    线程的生命周期
    线程的常用方法
    同步锁
    死锁
    通知和等待
    线程池

线程是什么

线程是最小的执行单元;程序,进程,线程;一个程序可以包含多个进程,一个进程可以包含多个线程。在java中可以让线程独立的执行一件事,而和其他的线程互不影响。线程简单理解就是可以同时去做多件事情。

在任务管理器中可以按到进程,如果想要看到线程的信息就需要通过工具类查看(jdk/bin/jconsole),如果要看更加详细的信息,或者针对程序做性能调整可以使用工具JProfiler

线程的创建方式

线程的创建方式有三种:

继承Thread类

package com.zlt.threads;

public class MyThread extends Thread{

	@Override
	public void run() {
		super.run();
		//线程的功能在这个run方法中进行实现
		for (int i = 0; i < 100; i++) {
			System.out.println("线程输出" + i);
		}
	}
	
	public static void main(String[] args) {
		//创建线程对象
		MyThread myThread = new MyThread();
		myThread.start();//启动线程
		for (int i = 0; i < 100; i++) {
			System.out.println("主线程输出" + i);
		}
	}
}

继承Thread类实现线程是最简单的一种实现方案,但是因为java是单继承,所以继承了Thread以后就无法再继承其他的类了,扩展性更差一些

实现Runnable接口

package com.zlt.threads;

public class MyRunnable implements Runnable{

	@Override
	public void run() {
		//线程实现的任务,在这个run方法中
		for (int i = 0; i < 100; i++) {
			System.out.println("runable线程输出" + i);
		}
	}

	public static void main(String[] args) {
		MyRunnable myRunnable = new MyRunnable();
		//启动线程依赖于Thread类
		MyThread thread = new MyThread(myRunnable);
		thread.start();//启动线程
		/*for (int i = 0; i < 100; i++) {
			System.out.println("主线程输出" + i);
		}*/
	}
}

创建一个类实现了Runnable接口,重写了接口中的run方法,创建对象后依赖于Thread去创建线程并执行run方法,需要注意的是,Thread的start执行后回去创建线程执行Thread的run方法,而Thread的run方法调用了Runnable中的run方法。这样Runnable中的run就可以在线程中执行起来了。因为是实现接口,扩展性更强。

实现Callable接口

package com.zlt.threads;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		//线程执行的任务
		for (int i = 0; i < 100; i++) {
			System.out.println("线程输出" + i);
		}
		return 100;
	}

	public static void main(String[] args) {
		MyCallable callable = new MyCallable();
		//FutureTask  实现了Runnable接口,里面有一个run方法,run方法调用了callable的call方法
		FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
		Thread thread = new Thread(futureTask);
		thread.start();
		try {
			//获取返回值只能在启动线程以后才可以获取
			Integer integer = futureTask.get();
			System.out.println(integer);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
}

FutureTask 实现了Runnable接口,里面有一个run方法,run方法调用了callable的call方法,call方法执行完毕获取到返回值,将返回值保存下来,可以通过get方法获取到call方法的返回值

创建线程如何选择哪一种

如果只是单纯的创建一个线程去执行任务,而不需要考虑其他的事情,可以使用继承Thread类

如果实现线程需要考虑扩展性,建议使用Runnable

如果需要知道线程执行完毕后的情况,建议使用Callable,只有Callable是有返回值的

线程的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DbQ7QrPj-1681732512728)(线程.assets/图片1.png)]

一般说生命周期就是说从创建开始到死亡,到底经历了一些什么事。

当创建了线程对象,线程处于新建状态,然后调用了start方法,线程处于就绪状态。就绪状态的线程会进入线程队列进行排队,当获取到cpu资源后,线程处于执行状态。在执行状态的线程,可能因为一些原因(IO阻塞,线程休眠等)进入阻塞状态,当阻塞原因消失后,线程会再次进入队列,等到cpu的资源,从上一次的中断处继续执行。当线程正常的执行完run方法则会进入死亡状态。

新建: 线程对象状态

就绪:线程进入线程队列,等待cpu的资源

执行:正在执行run方法

阻塞:主动释放cpu资源,等待阻塞原因消失

死亡:线程的执行结束,进入死亡状态

线程的常用方法

package com.zlt.threads;

public class MyRunnable implements Runnable{

	@Override
	public void run() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//线程实现的任务,在这个run方法中
		for (int i = 0; i < 100; i++) {
			System.out.println("runable线程输出" + i);
			
		}
	}

	public static void main(String[] args) {
		MyRunnable myRunnable = new MyRunnable();
		//启动线程依赖于Thread类
		Thread thread = new Thread(myRunnable,"线程A");//给线程取一个名字
		thread.getName();//获取线程的名字
		thread.setName("线程");//给线程设置一个名字
		// 线程中有三个优先级的常量 分别是  1  5  10  在设置线程优先级的时候建议使用常量设置   默认的优先级为5
		int priority = thread.getPriority();//获取当前线程的优先级
//		thread.setPriority(newPriority);//设置线程的优先级
		//设置线程的优先级 取值范围是1-10
//		Thread.MAX_PRIORITY  10
//		Thread.NORM_PRIORITY  5  默认的优先级
//		Thread.MIN_PRIORITY  1
		//设置是否为守护线程
		/*
		 守护线程也叫做后台线程,比较典型的守护线程就是垃圾回收线程。
		 主线程执行完毕后,还剩下其他的线程没有执行完毕则会等待其他的线程执行完毕,虚拟机才会结束
		 但是主线程执行完毕后,只剩下守护线程还没有执行完毕,虚拟机会直接结束,而不会等待守护线程执行完毕
		 true 表示是守护线程
		 false表示非守护线程  默认是false
		 */
//		thread.setDaemon(on);
		thread.isDaemon();//判断线程是否为守护线程
		//线程是否还活着  刚创建还没有调用start方法时 为false  调用了start后为true  run方法执行完毕后为false
		boolean alive = thread.isAlive();
		System.out.println(alive);
		
		// 调用了start0  而这个方法是一个本地方法,调用了其他的语言去启动线程,线程启动后再执行run方法
		thread.start();//启动线程
//		thread.stop();//停止线程已经过时,不建议使用,防止线程中的任务没有执行完毕 防止业务出现问题
		alive = thread.isAlive();
		System.out.println(alive);
		try {
			//让线程休眠 只有一个参数的方法,单位为毫秒
			Thread.sleep(1000);
			//让线程休眠,一个为毫秒,一个为纳秒
			Thread.sleep(1000,200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		Thread.yield();//让占用cpu资源的线程主动让出cpu的资源,处于就绪状态
		//中断线程  唤醒线程  如果线程处于休眠状态  可以唤醒休眠马上去执行任务  但是会抛出一个异常InterruptedException
//		thread.interrupt();
		
		//是否被中断
//		thread.isInterrupted()
		//获取当前占用cpu资源的线程对象  其实就是当前线程对象
		 Thread currentThread = Thread.currentThread();
		alive = currentThread.isAlive();
		System.out.println(alive);
	}
}

同步锁

当多个线程同时操作一个资源的时候,资源会出现紊乱的情况。这个时候希望同时只能有一个线程去操作,这个时候其他的线程不能操作,只能在旁边等着

可以使用同步锁来进行处理

同步锁是一个独占锁,一个线程加了锁以后,其他的线程就需要等待,而不能去进行处理。

同步方法
package com.zlt.threads;

public class Ticket implements Runnable{

	private int num = 10;//表示有十张票
	
	/**
	 * 在方法上添加了关键字synchronized 那么当 第一个线程调用这个方法的时候就会给这个方法添加一个锁,在执行完毕之前,
	 * 这个锁一直存在。其他的线程要想调用这个方法,只能等锁被释放以后才可以调用。当第一个线程执行完方法,释放锁以后,
	 * 所有要调用这个方法的线程都可以一起去抢夺调用权,谁抢到了谁又加锁,没有抢到的又继续等
	 * @return
	 */
	public synchronized boolean sell() {
		if(num > 0) {
			String name = Thread.currentThread().getName();
			System.out.println(name + "购买了第" + num + "张票");
			num--;
			return true;
		}else {
			return false;
		}
	}
	
	@Override
	public void run() {
		while(true) {
			if(!sell()) {
				break;
			}
		}
	}

	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread thread1 = new Thread(ticket, "a");
		Thread thread2 = new Thread(ticket, "b");
		Thread thread3 = new Thread(ticket, "c");
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

同步代码块
package com.zlt.threads;

public class Ticket implements Runnable{

	private int num = 10;//表示有十张票
	
	private Object lock = new Object();
	
	
	@Override
	public void run() {
		while(true) {
			//锁需要加在指定的对象上面,要确保多线程访问的是同一把锁,否则无法处理
			synchronized (lock) {
				if(num > 0) {
					String name = Thread.currentThread().getName();
					System.out.println(name + "购买了第" + num + "张票");
					num--;
				}else {
					break;
				}
			}
		}
	}

	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread thread1 = new Thread(ticket, "a");
		Thread thread2 = new Thread(ticket, "b");
		Thread thread3 = new Thread(ticket, "c");
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

需要注意的是,Thread.sleep 在线程休眠的时候是不会去释放锁的

同步锁,加锁和释放锁都是自动完成,而不需要程序员手动进行处理。

ReentrantLock

package com.zlt.threads;

import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable{

	private int num = 10;//表示有十张票

	//如果设置为true表示是公平锁,谁等的久,就轮到谁,如果不加参数或者为false就是非公平锁
	private ReentrantLock lock = new ReentrantLock(true); 
	
	
	@Override
	public void run() {
		while(true) {
			//锁需要加在指定的对象上面,要确保多线程访问的是同一把锁,否则无法处理
			lock.lock();//在这里加锁
			if(num > 0) {
				String name = Thread.currentThread().getName();
				System.out.println(name + "购买了第" + num + "张票");
				num--;
			}else {
				break;
			}
			lock.unlock();//在这个地方释放锁
		}
	}

	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		Thread thread1 = new Thread(ticket, "a");
		Thread thread2 = new Thread(ticket, "b");
		Thread thread3 = new Thread(ticket, "c");
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

ReentrantLock.tryLock() 尝试去加锁,如果没有加上锁就不加了,也可以传入超时时间,如果指定的时间内还没有加上锁,就不再等待

死锁

死锁发生的四个条件:

  1. 互斥条件,一个资源每次只能被一个线程使用,独木桥只能走一个人

  2. 请求与保持条件,一个线程因请求资源而阻塞,对已获得的资源保持不放, 甲不退出桥面,已也不退出桥面

  3. 不可剥夺条件,线程已经获取的资源,在未使用完成之前,不能强行剥夺,甲不能强制乙退,乙也不能强制甲退。

  4. 循环等待条件,若干个线程形成一个首尾相接的循环等待资源的关系,甲不退出,乙过不了,乙不退出,甲也过不了。

image-20210506161215517

上述的图中简单的描述了一个死锁的成因

死锁的demo

package com.zlt.thread;

public class DeadLock {

	private Object lockA = new Object();
	
	private Object lockB = new Object();
	
	Thread threadA = new Thread() {
		public void run() {
			synchronized (lockA) {
				System.out.println("A锁住A");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lockB) {
					System.out.println("A锁住B");
				}
			}
			
		};
	};
	
	Thread threadB = new Thread() {
		public void run() {
			synchronized (lockB) {
				System.out.println("B锁住B");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lockA) {
					System.out.println("B锁住A");
				}
			}
			
		};
	};
	
	public static void main(String[] args) {
		DeadLock deadLock = new DeadLock();
		deadLock.threadA.start();
		deadLock.threadB.start();
	}
}

如果使用ReentrantLock 可以使用唤醒的方式解决死锁问题

package com.zlt.threads;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDeadLockDemo {

	public static void main(String[] args) {
		ReentrantLock lock1 = new ReentrantLock();
		ReentrantLock lock2 = new ReentrantLock();
		Thread thread1 = new Thread(new DeadLock(lock1, lock2), "线程1");
		Thread thread2 = new Thread(new DeadLock(lock2, lock1), "线程2");
		thread1.start();
		thread2.start();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread1.interrupt();
	}
}

class DeadLock implements Runnable{
	
	private ReentrantLock lock1;
	
	private ReentrantLock lock2;
	
	public DeadLock(ReentrantLock lock1, ReentrantLock lock2) {
		super();
		this.lock1 = lock1;
		this.lock2 = lock2;
	}

	@Override
	public void run() {
		try {
			//这个锁是可以被唤醒的  唤醒后会抛出异常
			lock1.lockInterruptibly();
			Thread.sleep(100);
			lock2.lockInterruptibly();
			System.out.println(Thread.currentThread().getName() + "正常执行");
		} catch (InterruptedException e) {
			System.out.println(Thread.currentThread().getName() + "被唤醒了");
		} finally {
			lock1.unlock();
			lock2.unlock();
		}
	}
}

	

如何避免死锁?

在有些情况下死锁是可以避免的,介绍三种情况避免死锁:

  1. 加锁顺序(调整加锁的顺序)
  2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并且释放当前占用的锁)
  3. 死锁检测

通知和等待

在类Object中,有一些方法被子类继承了下来,这些方法在任何一个对象中都是存在的。

其中有五个方法是跟线程相关的,分别是

wait() 在一个线程中,调用了wait() ,线程会处于等待状态,直到调用了notify或者notifyAll方法通知以后才可以继续执行。如果不通知就一直等待。注意,需要是同一个对象的方法

wait(s) 在上面的基础上,如果没有通知,则达到指定的时间后,也会继续执行,而不会一直等待下去

wait(s,s1); 和一个参数的是一样的效果

notify() 通知,如果在当前对象等待的线程过多,随机唤醒

notifyAll(); 通知,如果在当前对象等待的线程过多,唤醒所有线程,然后所有的线程再一起去竞争资源

wait称为等待,notify 是通知

demo 两个线程交替输出字母和数字

package com.zlt.threads;

public class Test {
	
	private Object lock = new Object();

	/**
	 * 这个线程输出数字
	 */
	private Thread thread = new Thread() {
		public void run() {
			for (int i = 1; i <= 26; i++) {
				synchronized (lock) {
					lock.notify();
					System.out.println(i);
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	};
	
	/**
	 * 这个线程输出字母
	 */
	private Thread thread2 = new Thread() {
		public void run() {
			for (char i = 'A'; i <= 'Z'; i++) {
				synchronized (lock) {
					lock.notify();
					System.out.println(i);
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	};
	
	public static void main(String[] args) {
		Test test = new Test();
		test.thread.start();
		test.thread2.start();
	}
	
}

通知和等待都需要添加在同步锁中,否则会抛出异常IllegalMonitorStateException

线程池

在实际使用线程的时候,目前只要使用线程,就会涉及一个线程的完整生命周期,会涉及频繁的创建和销毁线程,创建与销毁从来都是最占用资源的。

线程池可以解决这个问题。事先先创建一些线程,放在那里,当有任务来的时候,从这些线程中取一个线程来执行任务,有其他任务来的时候再去取其他的线程来执行任务。如果所有的线程都在执行任务,则涉及扩容或者等待,直到有线程提交了任务,出现了空闲线程等待的任务才会继续执行。

public ThreadPoolExecutor(int corePoolSize,  核心线程数   编制数量
                          int maximumPoolSize, 最大线程数  工位数量
                          long keepAliveTime, 等待时间  线程空闲的时间,如果超过这个时间就会停止这个线程
                          TimeUnit unit, 等待单位
                          BlockingQueue<Runnable> workQueue, 任务队列  放任务的桌子
                          ThreadFactory threadFactory, 线程工厂 人事部
                          RejectedExecutionHandler handler) 拒绝策略  如何拒绝
                          
排队有三种通用策略: 
直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 

无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 

有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 


ThreadFactory  线程工厂  负责创建线程
    		默认情况下使用 Executors.defaultThreadFactory() 创建的线程是非守护线程  优先级为5
    	RejectedExecutionHandler
            RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略: 
           ThreadPoolExecutor.AbortPolicy 
                      用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException. 
            ThreadPoolExecutor.CallerRunsPolicy 
                      用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。 
            ThreadPoolExecutor.DiscardOldestPolicy 
                      用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。 
            ThreadPoolExecutor.DiscardPolicy 
                      用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。 

            如果上述的4种策略无法满足需求可以自行实现接口RejectedExecutionHandler 重写拒绝方法
            




需要记忆的方法

package com.zlt.threads;

import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPool {

	public static void main(String[] args) {
		
		
		
		ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(15, 20, 1, TimeUnit.MINUTES,
				new ArrayBlockingQueue<Runnable>(150), Executors.defaultThreadFactory(), 
				new ThreadPoolExecutor.AbortPolicy());
		//提交任务  参数是一个Runnable对象  如果线程数量已经达到最大,并且任务队列满了就会执行拒绝策略
//		poolExecutor.execute(command);
		//提交任务  但是可以提交Runnable和Callable 可以得到任务执行的反馈
		Future<Integer> submit = poolExecutor.submit(new Callable<Integer>() {

			@Override
			public Integer call() throws Exception {
				// TODO Auto-generated method stub
				return null;
			}
		});
//		submit.get()
		//立即结束线程池,未执行完成的任务,返回回来
		List<Runnable> shutdownNow = poolExecutor.shutdownNow();
		// 不会立刻停止线程池,而是需要把任务执行完毕
		poolExecutor.shutdown();
		//发起一个等待,在等待时间之内,当前线程不会继续往下,线程池执行完毕后,才会继续往下执行,或者等待的时间到了也会继续往下执行
//		poolExecutor.awaitTermination(timeout, unit)
		System.out.println();
        //获取活动线程数
		int activeCount = executor.getActiveCount();
		//批量提交任务的 参数是集合
//		executor.invokeAll(tasks)
		//执行一个,成功执行一个后,其他的任务取消
//		executor.invokeAny(tasks)
		//线程池是否关闭
		boolean shutdown = executor.isShutdown();
		//关闭线程池,但是会等任务执行完毕  但不会接受任何新任务
		executor.shutdown();
		//尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。
		List<Runnable> shutdownNow = executor.shutdownNow();
		// 移除任务
//		executor.remove(task)
	}
}

内置的线程池

缓存线程池,每个任务来的时候如果没有空闲线程都会创建新的线程,当线程空闲超过60s,这个线程会被销毁,一般要求短时间内处理大量的任务时可以使用
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
固长线程池,线程池中的数量是固定的,不会多创建,排队的队列是无边界的
 ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
单线程池,相当于固长线程池设置为1
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
延时线程池
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
		 //等待delay的时间后再去执行一次这个任务
//		 newScheduledThreadPool.schedule(command, delay, unit)
		 //在等待initialDelay 后第一次执行任务,然后在initialDelay+period后再执行一次,initialDelay+2*period 再执行一次以此类推
//		 newScheduledThreadPool.scheduleAtFixedRate(command, initialDelay, period, unit)

缓存线程池,每个任务来的时候如果没有空闲线程都会创建新的线程,当线程空闲超过60s,这个线程会被销毁,一般要求短时间内处理大量的任务时可以使用
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
固长线程池,线程池中的数量是固定的,不会多创建,排队的队列是无边界的
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
单线程池,相当于固长线程池设置为1
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
延时线程池
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
//等待delay的时间后再去执行一次这个任务
// newScheduledThreadPool.schedule(command, delay, unit)
//在等待initialDelay 后第一次执行任务,然后在initialDelay+period后再执行一次,initialDelay+2*period 再执行一次以此类推
// newScheduledThreadPool.scheduleAtFixedRate(command, initialDelay, period, unit)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员zhi路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值