本篇介绍Executor框架和线程池,内容总结摘抄自《Java并发编程实战》和《Java并发编程的艺术》。
在Java中,使用线程来异步执行任务。Java线程的创建与销毁需要一定的开销,如果我们为每个任务都创建一个新的线程来执行,尤其是当需要创建大量的线程时,这种方法就会存在一些缺陷:
- 线程生命周期的开销非常高。线程的创建过程需要时间,延迟处理的请求,并且需要JVM和操作系统提供一些辅助操作。如果请求的到达率非常高且请求的处理过程是轻量级的,例如大多数服务器应用程序就是这种情况,那么为每个请求创建一个新线程将消耗大量的计算资源。
- 资源消耗。活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程将闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量线程在竞争CPU资源时还将产生其他的性能开销。如果已经拥有足够多的线程使所有CPU保持忙碌状态,那么再创建更多的线程反而降低性能。
- 稳定性。在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且受多个因素制约,例如JVM启动参数、Thread构造函数中请求的栈大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,很可能抛出OutOfMemoryError异常。
Java的线程既是工作单元,也是执行机制。从JDK5开始,把工作单元与执行机制分离开来,工作单元包括Runnable和Callable,而执行机制由Executor框架提供。
Executor框架简介
Executor框架的两级调度模型
在HotSpot VM的线程模型中,Java线程被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程:当该Java线程终止时,这个操作系统线程也被回收。操作系统会调度所有线程并将它们分配给可用的CPU。
在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。这种两级调度模型的示意图如下图所示。
从图中看出,应用程序通过Executor框架控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。
Executor框架的结构
Executor框架主要由三大部分组成,如下:
- 任务。包括被执行任务需要实现的接口:Runnable接口或Callable接口。
- 任务的执行。包括任务执行机制的核心接口Executor接口,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口,即ThreadPoolExecutor类和ScheduledThreadPoolExecutor类。
- 异步计算的结果。包括接口Future接口和实现Future接口的FutureTask类。
Executor框架的主要类与接口的类图如下图:
以下是Executor框架包含的主要类与接口的介绍:
- Executor接口:是Executor框架的基础,将任务的提交与任务的执行分离开来。
- ThreadPoolExecutor类:是线程池的核心实现类,用来执行被提交的任务。
- ScheduledThreadPoolExecutor类:可以在指定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor类比Timer更灵活,功能更强大。
- Future接口和实现Future接口的FutureTask类:代表异步计算的结果。
- Runnable接口和Callable接口:它们的实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。
Executor框架的使用示意图如下:
主线程首先创建实现Runnable接口或Callable接口的任务对象。工具类Executors可以把一个Runnable对象封装为一个Callable对象(Executors的API下面会详细介绍)。
然后可以把Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnable)),或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable)或ExecutorService.submit(Callable<T>))。
如果执行ExecutorService.submit()方法,将返回一个实现Future接口的对象,例如FutureTask对象。由于FutureTask实现了Runnable,因此也可以直接创建FutureTask,然后直接交给ExecutorService执行。
最后,主线程可以执行FutureTask.get()方法来等待任务执行完成并获取执行结果,也可以执行FutureTask.cancel()方法来取消此次任务的执行。
Executors工具类
Executors工具类为Executor框架提供了一系列的工具方法,如下:
//将Runnable接口实例转换为Callable接口实例
public static Callable<Object> callable(Runnable task);
//创建一个按需创建的线程池,此前创建的线程在可用时将重用它们
public static ExecutorService newCachedThreadPool();
//创建一个固定线程数量的线程池,其任务队列是无界的LinkedBlockQueue队列
public static ExecutorService newFixedThreadPool(int nThreads);
//创建一个可以调度命令在指定延迟后运行或定期执行的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
//创建一个只有一个线程的线程池
public static ExecutorService newSingleThreadExecutor();
Executor接口
Executor接口是Executor框架的一个顶级接口,只有一个方法:
//在将来的某个时间执行给定的命令。 该命令可以在一个新线程,一个合并的线程中或在调用线程中执行,由Executor实现。
void execute(Runnable command);
ExecutorService接口
ExecutorService接口继承了Executor接口,包含以下方法:
//线程等待指定时间,超时后返回ExecutorService是否关闭
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
//执行指定的所有任务,一直阻塞直到所有任务执行结束,返回代表其执行结果的Future集合
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
//执行指定的所有任务,一直阻塞直到所有任务执行结束或超时,超时时没有执行完的线程不再执行且抛出异常
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException;
//返回最早正常执行结束的执行结果,只要有一个任务正常执行结束就返回,其他尚未执行结束的任务终止执行
//如果给定的所有任务中没有正常执行结束的任务,此方法抛出异常
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
//返回最早正常执行结束的执行结果,只要有一个任务正常执行结束就返回,其他尚未执行结束的任务终止执行
//如果给定的所有任务中没有正常执行结束的任务或者超时未返回,此方法抛出异常
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
//返回线程池是否关闭
boolean isShutdown();
//返回线程池状态是否为“TERMINATED”
boolean isTerminated();
//关闭线程池,调用这个方法后,线程池停止接受新的任务并等待已提交的任务执行结束后关闭线程池
void shutdown();
//关闭线程池,停止执行正在执行的任务,返回未执行完成的任务集合
List<Runnable> shutdownNow();
//提交并执行一个Callable任务,返回代表执行结果的Future
<T> Future<T> submit(Callable<T> task);
//提交并执行一个Runnable任务,返回代表执行结果的Future
Future<?> submit(Runnable task);
//提交并执行一个Runnable任务,返回代表执行结果的Future,将返回结果放到result对象中
<T> Future<T> submit(Runnable task, T result);
ThreadPoolExecutor详解
Exector框架最核心的类是ThreadPoolExecutor,它是线程池的实现类。ThreadPoolExecutor类的最多参数的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:线程池的核心线程数。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池的核心线程数时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
- maximumPoolSize:线程池最大数量。线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,那么线程池会再创建新的线程执行任务。如果使用了无界的任务队列,这个参数就没什么效果了。
- keepAliveTime:线程活动保持时间。线程池的工作线程空闲后,保持存活的时间。所以任务很多并且每个任务执行的时间比较短时,可以调大此参数来提高线程利用率。
- unit:线程活动保持时间的单位。可选的单位有DAYS(天)、HOURS(小时)、MINUTES(分钟)、MILLISECONDS(毫秒)、MICROSECONDS(微秒)和NANOSECONDS(纳秒)。
- workQueue:任务队列。用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:
1. ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序。
2. LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直出于阻塞状态,吞吐量通常高于LinkedBlockingQueue,静态方法Executors.newCachedThreadPool使用了这个队列。
4. PriorityBlockingQueue:一个具有优先级的无线阻塞队列。
- threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
- handler:饱和策略。当队列和线程池都满了,说明线程池出于饱和状态,那么必须采取一种策略处理提交的新任务。Java线程池框架提供了以下几种饱和策略:
1. AbortPolicy:直接抛出异常。
2. CallerRunsPolicy:只用调用者所在线程来运行任务。
3. DiscardOldestPolicy:丢弃队列里最近的任务,并执行当前任务。
4. DiscardPolicy:不处理,丢弃掉。
饱和策略在默认情况下是AbortPolicy策略,表示无法处理新任务时抛出异常。当然,也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。
部分源码分析
ThreadPoolExecutor使用一个AtomicIntager来存储线程池的状态和线程数量,其前3位表示线程池状态,后29位表示线程数量。
//一个32位的AtomicInteger存储线程池状态和线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer.SIZE=32,存储线程数量的位数29
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
线程池状态有如下几种:
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
- RUNNING:线程池被创建时即为此状态,该状态下,线程池可以接收新的任务并且处理阻塞队列的任务。
- SHUTDOWN:调用shutdown()方法后为此状态,该状态下,线程池不再接收新的任务但会处理阻塞队列的任务。
- STOP:调用shutdownNow()方法后为此状态,该状态下,线程池不再接收新的任务且不会处理阻塞队列的任务,还会中断正在执行的任务。
- TIDYING:所有的任务都执行完毕(即ctl=0时)为此状态,当线程池变为此状态时,将会调用terminated()方法。
- TERMINATED:调用terminated()方法后为此状态,线程池终止。
线程池的状态转换图如下:
ThreadPoolExecutor类的工作线程是由其内部类Worker实现的,Worker类继承了AbstractQueueSynchronizer类,继承了Runnable接口。ThreadPoolExecutor使用HashSet保存创建的工作线程:
private final HashSet<Worker> workers = new HashSet<Worker>();
部分Worker类的代码如下:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
//真正执行任务的线程
final Thread thread;
//提交的任务
Runnable firstTask;
//该工作线程完成的任务数量
volatile long completedTasks;
Worker(Runnable firstTask) {
//设置同步状态为-1,防止真正执行任务前中断
setState(-1);
this.firstTask = firstTask;
//使用defaultThreadFactory创建线程,将该Worker对象传入,真正执行的是Worker的run()方法
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
}
submit()方法定义在AbstractExecutorService抽象类中,传入Runnable或Callable。首先判断传入的对象是否为空,为空则抛空指针异常,否则继续执行。然后将传入的任务对象转换为RunnableFuture(该接口继承了Runnable和Callable接口),执行execute()方法,该方法在ExecutorService接口实现类中定义。最后,返回代表线程执行结果的Future。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
在上面介绍Executor框架的使用示意图时提到过,可以Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnable)),或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable)或ExecutorService.submit(Callable<T>))。在submit()方法中最终调用的还是execute()方法,我们来看execute()方法做了什么。
public void execute(Runnable command) {
//判断任务是否为空
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//判断当前线程池的线程数量是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//小于则调用addWorker()方法创建新线程,并使用新线程执行该任务
if (addWorker(command, true))
return;
c = ctl.get();
}
//判断如果线程池处出于RUNNING状态则将任务添加到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//判断线程池如果非RUNNING状态则从队列中删除任务
if (! isRunning(recheck) && remove(command))
//执行饱和策略
reject(command);
//否则判断线程池线程数量是否为空
else if (workerCountOf(recheck) == 0)
//为空则创建一个新的线程
addWorker(null, false);
}
//创建新线程失败则执行饱和策略
else if (!addWorker(command, false))
reject(command);
}
在executor()方法中调用addWorker()方法创建新线程并使用新线程执行该任务,addWorker()方法如下:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//当线程池状态不为RUNNING状态,并且线程池状态不为SHUTDOWN且提交的任务不为null
//且工作线程队列为空,则直接返回false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//自旋将工作线程数量加1
for (;;) {
int wc = workerCountOf(c);
//如果工作线程数量大于容量或大于核心线程数量,则直接返回false
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//使用CAS增加工作线程数量,如果增加失败,则继续自旋CAS增加
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
//不相等则代表线程状态变了,从retry处重新执行
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//新建工作线程对象
w = new Worker(firstTask);
//w.thread是真正执行任务的线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//加锁
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
//当线程池状态为RUNNING状态或是SHUTDOWN状态且提交任务为null时才满足条件
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
//检查线程状态,如果线程已经启动下面调用start()方法会抛异常。
if (t.isAlive())
throw new IllegalThreadStateException();
//将新建的工作线程加入到工作线程HashSet里
workers.add(w);
int s = workers.size();
//更新线程池的最大线程数
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
//解锁
mainLock.unlock();
}
//如果新增工作线程成功,则启动该线程执行提交的任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//新增工作线程失败,需要把工作线程数量减1
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
工作线程最终执行的还是Worker的runWorker()方法,runWorker()方法如下:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//Worker构造函数中将同步状态state初始化为-1,以防止在调用runWorker()方法前中断
//此处调用unlock()方法将state设置为0,只有state>=0时才允许中断
w.unlock();
boolean completedAbruptly = true;
try {
//循环来获取任务执行,当Worker中的任务为null时,调用getTask()方法获取任务
while (task != null || (task = getTask()) != null) {
//加锁
w.lock();
//当线程池状态为STOP、TIDYING或TERMINATED时,为此线程设置中断标识
//当线程池为其他状态时,线程中断标志为true且线程池状态为除以上三种以外的状态
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//执行任务的run()方法
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
//将task设置为null,在以后的循环中使用getTask()获取任务
task = null;
//更新工作线程的执行的任务数量
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//将该工作线程从HashSet中删除
processWorkerExit(w, completedAbruptly);
}
}
通过Executor框架的工具类Executors可以创建三种类型的ThreadPoolExecutor,分别为FixedThreadPool、SingleThreadExecutor和CachedThreadPool。
FixedThreadPool
FixedThreadPool被称为可重用固定线程数的线程池。Executors创建FixedThreadPool的方法参数如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。
FixedThreadPool的execute()方法的运行示意图如下:
- 如果当前运行的线程数少于corePollSize,则创建新的线程来执行任务。
- 在线程池完成预热之后(当前运行的线程数等于corePollSize),将任务加入到工作队列中。
- 线程执行玩1中的任务后,会在循环中反复从工作队列中获取任务来执行。
FixedThreadPool使用无界阻塞队列LinkedBlockingQueue作为线程池的工作队列,队列的容量为Integer.MAX_VALUE。使用无界队列作为工作队列会对线程池带来一些影响,如下:
- 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
- 由于以上原因,使用无界队列时,maximumPoolSize将是一个无效参数。
- 由于以上原因,使用无界队列时,keepAliveTime将是一个无效参数。
- 使用无界队列,运行中的FixedThreadPool不会拒绝任务。
SingleThreadExecutor
SingleThreadExecutor是使用单个worker线程的Executor,Executors创建SingleThreadExecutor的参数如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数与FixedThreadPool相同。SingleThreadExecutor同样使用了LinkedBlockingQueue作为线程池的工作队列。
SingleThreadExecutor的execute()方法运行示意图:
- 如果线程池中没有运行的线程,则创建一个新的线程来执行任务。
- 当线程池中有一个运行的线程后,将任务加入工作队列中。
- 线程执行完1中的任务后,会在一个无限循环中反复从工作队列中获取任务来执行。
CachedThreadPool
CachedThreadPool是一个根据需要创建新线程的线程池,Executors创建CachedThreadPool的方法参数如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool的corePoolSize被设置为0,maximumPoolSize被设置为Integer.MAX_VALUE,keepAliveTime设置为60L,即CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,超过这个时间线程就会被终止。
CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,即主线程提交任务必须对应着线程池中的线程的移除任务操作,但CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
CachedThreadPool的execute()方法运行的示意图如下:
- 首先执行SynchronousQueue.off()方法,如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll()方法,那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行步骤2。
- 当初试maximumPool为空,或者maximumPool中没有空闲线程时,将没有线程执行SynchronousQueue.poll()方法。这种情况下步骤1提交任务失败,此时CachedThreadPool会创建一个新的线程来执行任务,execute()方法执行完成。
- 在步骤2新创建的线程将任务执行完后,会执行SynchronousQueue.poll()方法。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒。如果60秒内主线程提交了一个任务,那么这个空闲线程将执行主线程提交的新任务,否则这个线程将被终止。由于空闲60秒的线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。
由于SynchronousQueue是一个没有容量的阻塞队列,因此每个插入操作必须等待另一个线程的对应移除操作。CachedThreadPool中任务传递的示意图如下图:
ScheduledThreadPoolExecutor详解
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,主要用来在指定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但ScheduledThreadPoolExecutor功能更强大、更灵活。Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。
ScheduledThreadPoolExecutor的运行机制
ScheduledThreadPoolExecutor的执行示意图如下图:
DelayQueue是一个无界队列,因此参数maximumPoolSize的大小没有效果。
ScheduledThreadPoolExecutor的执行主要分为两大部分。
- 当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask。
- 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。
ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下修改:
- 使用DelayQueue作为任务队列。
- 获取任务的方式不同。
- 执行周期任务后,增加了额外的处理。
ScheduledThreadPoolExecutor的实现
ScheduledFutureTask主要包含3个成员变量,如下:
//表示这个任务将要被执行的具体时间
private long time;
//表示这个任务被添加到ScheduledThreadPoolExecutor中的序号
private final long sequenceNumber;
//表示任务执行的间隔周期
private final long period;
DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时,time小的排在前面。如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面。即如果两个任务的执行时间相同,那么先提交的任务将被先执行。
ScheduledThreadPoolExecutor中得线程执行周期任务的过程如下图:
- 线程1从DelayQueue中调用DelayQueue.take()方法获取已到期的ScheduledFutureTask。到期任务是指ScheduledFutureTask的time大于等于当前时间。
- 线程1执行这个ScheduledFutureTask。
- 线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间。
- 线程1调用DelayQueue.add()方法把修改time之后的ScheduledFutureTask放回DelayQueue中。