通过之前对线程池和延迟队列的研究,我们现在来学习ScheduleThreadPoolExecutor(简称STPE)就比较轻松了。可以把STPE理解为这样一个线程池,它里面的任务可以延迟一次性执行(叫做定时任务),也可以每隔一个周期重复执行(周期任务)。
STPE继承了ThreadPoolExecutor类,所以它的线程池运作原理就是ThreadPoolExecutor那一套,只是任务队列用了延迟队列,这样就具有了延迟定时的功能。另外它的周期任务的实现是这样的,每次执行周期任务之后,会重新生成一个延迟任务到线程池中,线程池的任务数量就是可以保持不变,也就能让任务持续周期执行。
下面我们通过源码来佐证我以上的阐述。
一、基本代码机构
从以下代码中可以看到,STPE是继承于ThreadPoolExecutor,实现的ScheduleExecutorService是一个具有周期方法的接口。另外,可以看到STPE内部维护了一个延迟队列内部类,其就是STPE的任务队列,用来实现延迟功能的。
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
/** 当线程池shutdown后是否继续执行存在周期任务 */
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
/** 当线程池shutdown后是否继续执行存在的非周期任务(延迟任务) */
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
/**
* True if ScheduledFutureTask.cancel should remove from queue
*/
private volatile boolean removeOnCancel = false;
/** 任务计数器,可以保证先进先出 */
private static final AtomicLong sequencer = new AtomicLong();
/**
* Returns current nanosecond time.
*/
final long now() {
return System.nanoTime();
}
/** 调度的任务,这里省略 */
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {}
/** 内部实现的延迟队列,逻辑跟前面章节介绍的类似,省略 */
static class DelayedWorkQueue extends AbstractQueue<Runnable>
implements BlockingQueue<Runnable> {}
/** 构造器方法 */
public ScheduledThreadPoolExecutor(int corePoolSize) {
// 这里调用父类ThreadPoolExecutor的构造器,可以看到传了延迟队列DelayedWorkQueue
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
}
二、延迟任务
延迟任务方法是schedule() ,它不会周期执行,只是在延迟一定时间后执行一次。下面是比较核心的几个方法,它可能不是在同一个类中,如果要看具体的代码位置建议打开jdk源码一起阅读。
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
// 任务对象,这个对象我们比较关心的是run和compareTo方法,后面会讲解
// decorateTask就是一个装饰器方法,这里它直接返回了ScheduleFutureTask对象,triggerTime方法会对延迟时间做边界控制,防止溢出
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
// 这里是提交任务,没太多的逻辑
delayedExecute(t);
return t;
}
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}
// 对延迟时间做校验,防止溢出。如果delay大于Long.MAX_VALUE >> 1,则做溢出的校验
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
/** 对大的延迟时间做溢出校验 */
private long overflowFree(long delay) {
Delayed head = (Delayed) super.getQueue().peek();
if (head != null) {
long headDelay = head.getDelay(NANOSECONDS);
/* 这里考虑到一种情况,当入队列时,头节点调用getDelay方法小于0,
并且delay - headDelay 小于0的话,那么compareTo方法时,
可能会生产溢出。具体可以看下一个方法compareTo的
long diff = time - x.time; 这一行代码*/
if (headDelay < 0 && (delay - headDelay < 0))
delay = Long.MAX_VALUE + headDelay;
}
return delay;
}
/** 这个比较方法是为了构建最小堆的数据结构 */
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
/** 延迟方法的构造器中,period=0,说明不是周期任务 */
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
// 延迟任务会走这个分支,即调用父类的run方法,那么它就跟普通的线程池方法类似了
else if (!periodic)
ScheduledFutureTask.super.run();
// 周期任务走这个分支,后面分析代码
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
/** 判断是否为周期任务 */
public boolean isPeriodic() {
return period != 0;
}
/** 提交延迟任务 */
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task);
else {
// 任务添加到任务队列,不是直接执行,而是由工作线程之后去队列里获取任务执行
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
/** 判断是否需要增加一个执行任务的线程 */
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
三、周期任务
周期任务的逻辑主要看ScheduledFutureTask类的run方法,最后一个分支就是周期任务。它首先会执行一次任务,然后再往任务队列添加下一次执行的任务。
// run方法的分支,runAndReset方法会执行一次任务,然后设置下一次任务的触发时间,最后把新的任务加到任务队列中,等待调度执行
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
/** 这里就是设置下一次执行的时间,直接加上周期的时间*/
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
/** 提交下一次执行的任务到队列中 */
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
四、总结
ScheduledThreadPoolExecutor线程池原理大概可以为总结如下两点:
- 延迟功能依赖于延迟队列的最小堆数据结构,延迟执行的实现是基于定时的时间跟当前时间做比较。
- 周期任务是执行当前任务后,再往任务队列添加一个定时任务。