一、简介
ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,继承了父类对线程的管理维护功能,通过还可以执行延迟和定时任务。
/*
* This class specializes ThreadPoolExecutor implementation by
*
* 1. Using a custom task type, ScheduledFutureTask for
* tasks, even those that don't require scheduling (i.e.,
* those submitted using ExecutorService execute, not
* ScheduledExecutorService methods) which are treated as
* delayed tasks with a delay of zero.
*
* 2. Using a custom queue (DelayedWorkQueue), a variant of
* unbounded DelayQueue. The lack of capacity constraint and
* the fact that corePoolSize and maximumPoolSize are
* effectively identical simplifies some execution mechanics
* (see delayedExecute) compared to ThreadPoolExecutor.
*
* 3. Supporting optional run-after-shutdown parameters, which
* leads to overrides of shutdown methods to remove and cancel
* tasks that should NOT be run after shutdown, as well as
* different recheck logic when task (re)submission overlaps
* with a shutdown.
*
* 4. Task decoration methods to allow interception and
* instrumentation, which are needed because subclasses cannot
* otherwise override submit methods to get this effect. These
* don't have any impact on pool control logic though.
*/
根据注释,也可以了解其相对于ThreadPoolExecutor 的变化:
- 使用内部类ScheduledFutureTask封装任务
- 使用内部类DelayedWorkQueue作为线程池队列
- onShutdown方法基于参数配置化去处理shutdown后的任务
- 提供decorateTask方法作为ScheduledFutureTask的修饰方法,以便使用者进行扩展
二、ScheduledThreadPoolExecutor属性
/**
* False if should cancel/suppress periodic tasks on shutdown.
*/
//shutdown后是否继续执行定时任务
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
/**
* False if should cancel non-periodic tasks on shutdown.
*/
//shutdown后是否继续执行延迟任务
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
/**
* True if ScheduledFutureTask.cancel should remove from queue
*/
//cancle方法收费需要将该任务从队列移除
private volatile boolean removeOnCancel = false;
/**
* Sequence number to break scheduling ties, and in turn to
* guarantee FIFO order among tied entries.
*/
//任务的序列号
private static final AtomicLong sequencer = new AtomicLong();
三、ScheduledThreadPoolExecutor构造方法
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
与父类的构造方法相比,最大线程数maximumPoolSize固定为Integer.MAX_VALUE,最大活跃时间keepAliveTime固定为0,队列workQueue固定为DelayedWorkQueue。
四、DelayedWorkQueue
DelayedWorkQueue是BlockingQueue的实现类,是专门为了ScheduledThreadPoolExecutor设计的队列,队列中的数据结构为二叉树,每个节点的值小于其子节点的值。初始容量为16,节点数量大于数组长度后触发扩容,容量变为原来的1.5倍。
1. 属性
private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
private int size = 0;
private final Condition available = lock.newCondition();
INITIAL_CAPACITY为初始容量,queue数组用于存放节点。lock锁和available用于控制并发和阻塞等待,size表示节点数量。
private Thread leader = null;
leader属性用于表示正在阻塞等待头结点的线程,是一种Leader-Follower模式的变种,可以最小化线程的等待时间。同样通过阻塞方式去获取头结点,那么leader线程的等待时间为头结点的延迟时间,其它线程则会陷入阻塞状态(available.await())。leader线程获取到头结点后需要发送信号唤醒其它线程(available.asignAll())。
2. 插入节点
以DelayedWorkQueue#offer(Runnable)为例,其它插入节点的方法都是通过调用该方法完成
public boolean offer(Runnable x) {
//空值校验
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length)
//扩容
grow();
size = i + 1;
//插入的是第一个节点
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
siftUp(i, e);
}
//插入的是头结点,不需要leader,唤醒等待头结点的阻塞线程
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
(1)DelayedWorkQueue#grow()
表示扩容,数组长度修改为原来的1.5倍后复制数组中的元素。
private void grow() {
int oldCapacity = queue.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
if (newCapacity < 0) // overflow
newCapacity = Integer.MAX_VALUE;
queue = Arrays.copyOf(queue, newCapacity);
}
(2)DelayedWorkQueue#setIndex()
setIndex(e, 0)用于修改ScheduledFutureTask的heapIndex属性,表示该对象在队列里的下标。
private void setIndex(RunnableScheduledFuture<?> f, int idx) {
if (f instanceof ScheduledFutureTask)
((ScheduledFutureTask)f).heapIndex = idx;
}
(3)DelayedWorkQueue#siftUp(int, RunnableScheduledFuture<?>)
用于在插入新节点后对二叉树进行调整,进行节点上移,保持其特性(节点的值小于子节点的值)不变。
private void siftUp(int k, RunnableScheduledFuture<?> key) {
while (k > 0) {
int parent = (k - 1) >>> 1;
RunnableScheduledFuture<?> e = queue[parent];
if (key.compareTo(e) >= 0)
break;
queue[k] = e;
setIndex(e, k);
k = parent;
}
queue[k] = key;
setIndex(key, k);
}
从插入的节点开始循环比较,节点小于父节点,则上移。
3. 删除节点
DelayedWorkQueue#remove(Runnable)为例,其它删除方法也是通过调用盖方法完成。
public boolean remove(Object x) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = indexOf(x);
//节点元素不存在
if (i < 0)
return false;
//修改元素的heapIndex
setIndex(queue[i], -1);
int s = --size;
//末节点作为替代节点
RunnableScheduledFuture<?> replacement = queue[s];
queue[s] = null;
if (s != i) {
//下移,保证该节点的子孙节点保持特性
siftDown(i, replacement);
//上移,保证该节点的祖先节点保持特性
//上移和下移不可能同时发生,替代节点大于子节点时下移,否则上移
if (queue[i] == replacement)
siftUp(i, replacement);
}
return true;
} finally {
lock.unlock();
}
}
(1)DelayedWorkQueue#indexOf(Object)
查找对象在队列中的下标。
private int indexOf(Object x) {
if (x != null) {
if (x instanceof ScheduledFutureTask) {
int i = ((ScheduledFutureTask) x).heapIndex;
// Sanity check; x could conceivably be a
// ScheduledFutureTask from some other pool.
if (i >= 0 && i < size && queue[i] == x)
return i;
} else {
for (int i = 0; i < size; i++)
if (x.equals(queue[i]))
return i;
}
}
return -1;
}
ScheduledFutureTask根据其heapIndex属性,其它对象则进行遍历比较。
(2)DelayedWorkQueue#siftDown(int, RunnableScheduledFuture<?>)
private void siftDown(int k, RunnableScheduledFuture<?> key) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
RunnableScheduledFuture<?> c = queue[child];
int right = child + 1;
if (right < size && c.compareTo(queue[right]) > 0)
c = queue[child = right];
if (key.compareTo(c) <= 0)
break;
queue[k] = c;
setIndex(c, k);
k = child;
}
queue[k] = key;
setIndex(key, k);
}
循环,选择当前节点的子节点中较小的节点与当前节点进行比较,如果当前节点小于子节点,则下移。
(3)非阻塞获取头结点
DelayedWorkQueue#poll()
public RunnableScheduledFuture<?> poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
RunnableScheduledFuture<?> first = queue[0];
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return finishPoll(first);
} finally {
lock.unlock();
}
}
private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
int s = --size;
RunnableScheduledFuture<?> x = queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
setIndex(f, -1);
return f;
}
可以看到
(1)队列中没有元素:返回null。
(2)头结点的延迟时间没到:返回null。
(3)头结点达到延迟时间:末节点作为替代节点下移调整二叉树结构后返回头结点。
(4)阻塞获取头结点
以DelayedWorkQueue#take()为例,DelayedWorkQueue#poll(long, TimeUnit)类似,ScheduledThreadPoolExecutor的延迟执行功能就是通过阻塞获取头结点实现的。
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
//已经存在leader线程,当前线程await阻塞
available.await();
else {
//当前线程作为leader线程,并制定头结点的延迟时间作为阻塞时间
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
//leader线程阻塞结束
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//leader线程没有阻塞,可以找到头结点,唤醒阻塞线程
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
五、ScheduledFutureTask
在ScheduledThreadPoolExecutor中,所有的任务都会被封装成ScheduledFutureTask,无论是runnable还是callable,无论是否需要延迟和定时。
1. 属性
/** Sequence number to break ties FIFO */
//序列号
private final long sequenceNumber;
/** The time the task is enabled to execute in nanoTime units */
//执行时间
private long time;
/**
* Period in nanoseconds for repeating tasks. A positive
* value indicates fixed-rate execution. A negative value
* indicates fixed-delay execution. A value of 0 indicates a
* non-repeating task.
*/
//定时周期
private final long period;
/** The actual task to be re-enqueued by reExecutePeriodic */
//实际任务
RunnableScheduledFuture<V> outerTask = this;
/**
* Index into delay queue, to support faster cancellation.
*/
//在队列中的下标
int heapIndex;
2. ScheduledFutureTask#compareTo(Delayed)
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;
}
ScheduledFutureTask需要放入DelayedWorkQueue,而DelayedWorkQueue是从小到大进行排序的。
在ScheduledFutureTask,其大小的排序根据执行时间time正序排列,如果相同,在按照序列号sequenceNumber 正序排列。
3. ScheduledFutureTask#run()
ScheduledFutureTask由任务封装而来,执行任务就是执行ScheduledFutureTask#run()方法,ScheduledThreadPoolExecutor的周期执行的功能也是在run方法中实现。
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
(1)判断任务是否周期执行
public boolean isPeriodic() {
return period != 0;
}
(2)检查当前状态能否执行任务
boolean canRunInCurrentRunState(boolean periodic) {
return isRunningOrShutdown(periodic ?
continueExistingPeriodicTasksAfterShutdown :
executeExistingDelayedTasksAfterShutdown);
}
final boolean isRunningOrShutdown(boolean shutdownOK) {
int rs = runStateOf(ctl.get());
return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
}
线程池处于RUNNING则必然可以执行,SHUTDOWN状态则需要检查对应的参数设置。
(3)如果不能执行,取消任务
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = super.cancel(mayInterruptIfRunning);
if (cancelled && removeOnCancel && heapIndex >= 0)
remove(this);
return cancelled;
}
执行任务的取消逻辑(FutureTask的cancel方法),然后根据removeOnCancel参数设置确定是否需要从队列移除。
(4)非周期任务直接执行
ScheduledFutureTask.super.run()即FutureTask的run方法。
(5)周期任务的执行
ScheduledFutureTask.super.runAndReset()即FutureTask的runAndReset方法,与run方法的不用就是正常完成后任务的状态不会变化,依旧是NEW,且返回值为成功或失败,不会设置result属性。
需要注意,如果本次任务执行出现异常,返回false,后续的该任务不会再执行。
(6)周期任务的下一次执行时间
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
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);
//发生溢出
if (headDelay < 0 && (delay - headDelay < 0))
delay = Long.MAX_VALUE + headDelay;
}
return delay;
}
这儿可以看到,周期时间period有正有负,这是ScheduledThreadPoolExecutor的ScheduledAtFixedRate和ScheduledWithFixedDelay的方法区别,前者为正数,后者为负数。
正数时,下一次执行时间为原来的执行时间+周期,即以执行开始时间为基准。
负数时,不考虑溢出情况,下一次执行时间为当前时间+周期,即以执行结束时间为基准。
如果溢出,下一次执行时间为Long.MAX_VALUE + headDelay。
为什么要考虑溢出?
因为在任务加入队列时要将该任务与二叉树中的部分原有任务在siftUp中通过compareTo进行比较,compareTo比较的是time的差值,如果出现溢出,会导致队列排序不正常,任务的执行混乱的结果。
为什么只考虑向上溢出,不需要考虑向下溢出
因为compareTo中比较的是time的差值,time都是long类型的正数,两个long类型的正数相减显然不会出现向下溢出。
溢出的判断条件是什么?
compareTo中比较的是time的差值,相当于两个节点getDelay的差值。差值最大的必然的该任务与队列头结点任务的差值。因此要判断会不会出现溢出,只需要判断delay - headDelay会不会溢出,delay必然是正数,要出现溢出则headDelay必为负数(也就是任务可以出队列但没有出队列,任务没有及时得到执行)的情况,因此判断条件就是:headDelay < 0 且 (delay - headDelay > Long.MAX_VALUE),即headDelay < 0 && (delay - headDelay < 0)。
为什么delay < (Long.MAX_VALUE >> 1)时不需要考虑溢出?
没想明白
为什么period为正数时不需要考虑溢出?
因为conpareTo比较的是time的差,假设当前任务的time为A,当前队列头结点任务的time为B,因为A比B先出队列,所以A < B。显然A + period - B < period < Long.MAX_VALUE,不会出现溢出的情况。
(7)任务的下一次执行安排
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
如果当前线程池状态可以执行周期任务,加入队列,并开启新线程。
FutureTask的解析可以参考线程池源码解析之FutureTask。
六、ScheduledThreadPoolExecutor执行任务
在对DelayedWorkQueue和ScheduledFutureTask这两个内部类进行解析后,正式针对ScheduledThreadPoolExecutor的方法进行解析。
1. ScheduledThreadPoolExecutor#schedule
(1)延迟执行callable任务
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
(2)延迟执行runnable任务
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
2. scheduleAtFixedRate
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
延迟且周期执行,到点执行,开始执行时间 = 上一次的执行开始时间 + period。但是任务执行完成之后,下一次任务才能加入队列。所以准确得说,开始执行时间 = max(上一次执行开始时间 + period, 上一次执行结束时间)
3. ScheduledThreadPoolExecutor#scheduleWithFixedDelay
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
延迟且周期执行,到点执行,固定的延迟时间,开始执行时间 = 上一次任务执行结束时间 + delay。
与scheduleAtFixedRate的区别是通过传入ScheduledFutureTask的period的正负情况确定的,具体实现方式已经在上文ScheduledFutureTask的分析中提到。
4. ScheduledFutureTask#decorateTask(Runnable, RunnableFutureTask)
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
没有做任何操作,直接将task返回。
该方法主要目的是用于子类扩展。
5. ScheduledFutureTask#delayedExecute(RunnableScheduledFuture)
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();
}
}
校验状态,提交任务,调用ensurePrestart()方法开启线程。
6. ScheduledFutureTask#ensurePrestart()
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
//corePoolSize = 0的情况,至少开启一个线程
else if (wc == 0)
addWorker(null, false);
}
检验运行线程数量,开启新线程
7. ScheduledFutureTask#onShutdown()
线程池状态由RUNNING >> SHUTDOWN变化时的钩子函数。
void onShutdown() {
BlockingQueue<Runnable> q = super.getQueue();
boolean keepDelayed =
getExecuteExistingDelayedTasksAfterShutdownPolicy();
boolean keepPeriodic =
getContinueExistingPeriodicTasksAfterShutdownPolicy();
if (!keepDelayed && !keepPeriodic) {
for (Object e : q.toArray())
if (e instanceof RunnableScheduledFuture<?>)
((RunnableScheduledFuture<?>) e).cancel(false);
q.clear();
}
else {
// Traverse snapshot to avoid iterator exceptions
for (Object e : q.toArray()) {
if (e instanceof RunnableScheduledFuture) {
RunnableScheduledFuture<?> t =
(RunnableScheduledFuture<?>)e;
if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
t.isCancelled()) { // also remove if already cancelled
if (q.remove(t))
t.cancel(false);
}
}
}
}
tryTerminate();
}
keepDelayed :shutdown后是否继续执行延迟任务
keepPeriodic :shutdown后是否继续执行周期任务
将不需要执行的延迟任务和周期任务进行取消并从队列中移除。
最后在检查tryTerminate()方法。