1 前言
ScheduledThreadPoolExecutor
是定时任务执行器,它可以通过构造方法创建,也可通过工厂类Executors的静态方法创建(本文基于JDK1.8)。
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,并实现了表示定时执行器的接口ScheduledExecutorService。它主要用来在给定的延迟之后运行任务,或者定期执行任务。(要弄懂ScheduledThreadPoolExecutor,必须先了解父类ThreadPoolExecutor的大致实现,之前的文章已经对ThreadPoolExecutor做过分析,这里不再赘述)
ScheduledExecutorService接口的抽象方法说明:
public interface ScheduledExecutorService extends ExecutorService {
//在给定的延时后执行一次任务
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
//在给定的延时后执行一次任务
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
//在给定的(initialDelay)初始化延时后以固定频率周期性(period是每次任务开始的时间间隔)执行任务
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
TimeUnit unit);
//在给定的(initialDelay)初始延时后以固定延迟周期性(period是一次任务结束与下次任务开始的时间间隔)执行任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,
TimeUnit unit);
}
ScheduledThreadPoolExecutor会把待调度的任务ScheduledFutureTask放到一个DelayedWorkQueue中。我们来了解一下队列DelayedWorkQueue。
2 静态内部类DelayedWorkQueue
DelayedWorkQueue封装了一个RunnableScheduledFuture数组,它利用这个数组实现一个基于堆结构的优先级队列,其原理与PriorityQueue类似。
这个类是专门用来存放RunnableScheduledFuture任务的队列,此队列的出队顺序与一般的队列的先进先出不同,它按照优先级高低顺序出队,即优先级高的元素先出队。 这里优先级的定义是: 执行时间早的任务优先级高,若是同样早就比较任务的提交时间,先提交的任务优先级高。另外,只有当任务的剩余延时不大于零时,任务才可能成功出队,否则将一直等待直到其剩余延时不大于零。
成员变量
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 Thread leader = null;
private final Condition available = lock.newCondition();
leader:在队列头部等待任务出队的线程。
available属性:新任务在队列头部可获取(延时已到)或新的线程成为leader属性时,可使用此Condition条件发出通知信号。
3 成员内部类ScheduledFutureTask
ScheduledFutureTask是ScheduledThreadPoolExecutor的一个成员内部类,它继承了FutureTask类,另外还实现了表示周期性任务的ScheduledFutureTask接口。
(这里定义成员内部类而不是静态内部类的好处在于:成员内部类对象与外部类的实例对象相互绑定,此内部类的实例对象可直接访问外部实例的全局变量和方法。ScheduledFutureTask的许多方法都会访问外部类ScheduledThreadPoolExecutor的属性与方法)
1) 成员变量
ScheduledFutureTask有5个成员变量。
private final long sequenceNumber;
private long time;
private final long period;
RunnableScheduledFuture<V> outerTask = this;
int heapIndex;
- long型成员变量time,表示这个任务将要被执行的具体(绝对)时间(以纳秒为单位)。
- long型成员变量sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的编号(任务提交先后的序号)。
- long型成员变量period,表示任务执行的间隔周期(以纳秒为单位)。 正值表示fixed-rate执行任务的时间间隔(每次任务开始的时间间隔)。 负值表示fixed-delay执行任务的时间间隔(一次任务结束与下次任务开始的时间间隔)。 值为0表示非周期性任务(只执行一次的任务)。
- outerTask:表示定时任务本身,它会被外部类ScheduledThreadPoolExecutor的reExecutePeriodic方法重新入队。
- heapIndex: 定时任务在队列中堆索引,即数组的下标。(优先级队列使用”物理上为数组、逻辑上是堆“的数据结构存放元素)
2) 构造方法
构造方法逻辑简单,它们只涉及对实例变量的初始化,
//创建一个只执行一次的延时任务
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
//getAndIncrement是外部类ScheduledThreadPoolExecutor的静态变量
this.sequenceNumber = sequencer.getAndIncrement();
}
//创建一个多次执行的周期性任务
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
this.sequenceNumber = sequencer.getAndIncrement();
}
//创建一个只执行一次的延时任务
ScheduledFutureTask(Callable<V> callable, long ns) {
super(callable);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
3) 方法
(1) getDelay(TimeUnit)
getDelay返回当前定时任务(对象)的剩余延迟(即多少时间后将立即执行任务).若返回值为非正数时,表示此任务可以出队了.
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);//now()返回当前的绝对时间(纳秒)
}
(2) compareTo(Delayed)
compareTo方法用于确定任务的优先级,此方法会被优先级队列DelayedWorkQueue作为“入队位置、出队先后“的参考依据。
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
//与自身比,返回0
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;
}
//不是ScheduledFutureTask类型任务,就根据任务的剩余延迟决定入队的的位置
//剩余延迟小的任务排在队列前
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
(3) isPeriodic()
isPeriodic方法返回任务是否是(会被多次执行)周期性任务的布尔值
public boolean isPeriodic() {
return period != 0;
}
(4) setNextRunTime()
setNextRunTime设置周期性任务的下次执行时间(单次执行的定时任务不会用到此方法)
private void setNextRunTime() {
long p = period;
if (p > 0)
//period为正,表示fixed-rate执行任务的时间间隔(每次任务开始的时间间隔)
//只需要time加上period间隔就行
time += p;
else
//period负值表示fixed-delay执行任务的时间间隔(一次任务结束与下次任务开始的时间间隔)
//需要调用外部类的triggerTime计算出结果,time=当前时间+周期间隔。
time = triggerTime(-p);
}
long triggerTime(long delay) {
//overflowFree主要是将延迟时间限制在Long.MAX_VALUE范围内
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
(4) cancel(boolean)
cancel方法取消任务的执行
public boolean cancel(boolean mayInterruptIfRunning) {
//在任务取消后,从队列中移除此任务
boolean cancelled = super.cancel(mayInterruptIfRunning);
//removeOnCancel是外部类的属性,表示在任务被取消后是否从并且队列中移除此任务
if (cancelled && removeOnCancel && heapIndex >= 0)
romove是ScheduledThreadPoolExecutor的父类ThreadPoolExecutor中的方法
remove(this);
return cancelled;
}
(5) run()
run方法是定时执行的具体内容
主要逻辑:
①若当前状态不能执行任务,就取消任务。反之进入下一步
②若当前任务是非周期性任务,就执行父类的run方法 。反之(即当前任务是周期性任务),进入下 一步
③执行父类的runAndReset()方法,若runAndReset成功就设置任务的下次执行时间,并将此任务重新入队。
public void run() {
boolean periodic = isPeriodic();
//当前状态不能执行任务,就取消任务
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
//当前状态可以执行任务,且任务是单次执行的定时任务,就调父类的run方法
ScheduledFutureTask.super.run();
//当前状态可以执行任务,且任务是周期性任务,就调父类的runAndReset()方法
else if (ScheduledFutureTask.super.runAndReset()) {
//runAndReset成功,设置周期性任务的下次执行时间,并将此任务重新放入队列中
//reExecutePeriodic是外部类的方法,会在下面介绍它
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
reExecutePeriodic用于入周期性任务的重新入队。主要逻辑是:①若当前状态可执行任务,就将任务入队。在任务入队后,②若当前状态不可执行任务且从队列中成功移除任务,则取消此任务。③反之则启动新Worker线程(此线程会等待任务队列中任务出队,然后执行任务) 。
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();//有效线程数<corePoolSize,启动一个新线程
}
}
4 构造方法与字段
(1)构造方法
ScheduledThreadPoolExecutor有四个构造方法,ScheduledThreadPoolExecutor
的构造方法逻辑十分简单,它们直接调用父类实现。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
任务队列DelayedWorkQueue是无界的,此队列永不会满,父类的成员变量maximumPoolSize、keepAliveTime没有意义。
(2)成员变量
//False if should cancel/suppress periodic tasks on shutdown.
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
/**
* False if should cancel non-periodic tasks on shutdown.
*/
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
/**
* True if ScheduledFutureTask.cancel should remove from queue
*/
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();
continueExistingPeriodicTasksAfterShutdown
: 表示shutdown之后周期性任务是否应继续执行,默认为false,可使用setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean)
方法改变这种默认行为。
executeExistingDelayedTasksAfterShutdown
: 表示shutdown之后正在执行的非周期性任务是否应执行,默认为true,可使用setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean)
方法改变这种默认行为。
removeOnCancel
:任务被取消后是否应从队列中移除 ,默认为true,可使用setRemoveOnCancelPolicy(boolean)
改变这个属性的值 (此属性会被ScheduledFutureTask.cancel用到) 。
sequencer
:任务的序号,就是任务提交的先后顺序。(有些不懂这里为什么用静态变量,而不是用实例变量)
5 主要方法
1) schedule系列方法
schedule(Runnable,long,TimeUnit )
用于提交(只执行一次)非周期性的延时任务,它不需要结果(最终返回的结果为null)。
schedule(Runnable,long ,TimeUnit )
也用于提交(只执行一次)非周期性的延时任务,但它需要结果。
两个schedule方法的主要逻辑相同:将原始任务(Runnable/Callable)包装成RunnableScheduledFuture
任务,再调用delayedExecute延迟执行任务,最后再返回此任务。
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
//将Runnable包装成RunnableScheduledFuture任务
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));// triggerTime计算下次任务执行的具体时间
delayedExecute(t);//延迟执行任务
return t;
}
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
//将Callable包装RunnableScheduledFuture任务
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
triggerTime(delay, unit)));
delayedExecute(t);//延迟执行任务
return t;
}
scheduleAtFixedRate
在给定的(initialDelay)延时后以固定频率周期性(period是每次任务开始的时间间隔)地执行任务
scheduleWithFixedDelay
在给定的(initialDelay)延时后以固定延时周期性(period是一次任务结束与下次任务开始的时间间隔)地执行任务
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);
//decorateTask方法直接返回参数sft,所以这里的t和sft是同一个对象,即sft.out=sft;
//"sft.out=sft"在对象初始化时就做这件事儿,所以在这里没啥意义。
//但如果decorateTask方法被子类重写了,这种默认行为就会改变
sft.outerTask = t;//初始化sft的outerTask属性,将outerTask属性自指
delayedExecute(t);//延时执行任务
return t;
}
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);
//decorateTask方法直接返回参数sft,所以这里的t和sft是同一个对象;即sft.out=sft;
sft.outerTask = t;//初始化sft的outerTask属性,将outerTask属性自指
delayedExecute(t);//延时执行任务
return t;
}
(1) decorateTask
decorateTask方法啥也没干,就做了一个向上转型,但子类中可重写此方法以改变这种默认的行为。
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
protected <V> RunnableScheduledFuture<V> decorateTask(
Callable<V> callable, RunnableScheduledFuture<V> task) {
return task;
}
schedule系列方法的核心逻辑都是交给delayedExecute实现的,我们来看看delayedExecute做了些什么工作。
(2) delayedExecute
delayedExecute的主要逻辑:
①若执行器已关闭,使用拒绝策略处理新任务。反之则进入下一步
②将新任务入队列。在任务入队后,若执行器被关闭 &&当前状态不可执行任务&&从队列中成功移除任务时,就取消此任务。反之则进入下一步(大多数情况都是“反之”这种条件)
③若当前有效线程数小于corePoolSize,则启动新Worker线程(此线程会等待任务队列中任务出队,然后执行任务) 。
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
//反之就启动一个新Worker线程,此线程会到任务队列中等待任务出队(若当前有效线程数大于corePoolSize,不启动新线程)
ensurePrestart();//父类ThreadPoolExecutor中的方法
}
}
(3) canRunInCurrentRunState(boolean)
canRunInCurrentRunState方法根据任务的类型判断执行器关闭后是否执行任务。
boolean canRunInCurrentRunState(boolean periodic) {
//周期性任务返回continueExistingPeriodicTasksAfterShutdown(默认为false)
//非周期性任务返回continueExistingPeriodicTasksAfterShutdown(默认为true)
return isRunningOrShutdown(periodic ?
continueExistingPeriodicTasksAfterShutdown :
executeExistingDelayedTasksAfterShutdown);
}
2)execute与submit系列方法
ScheduledThreadPoolExecutor重写了父类ThreadPoolExecutor的execute、submit方法。
这里的execute、submit都是委托给schedule( command,delay,unit)方法实现的,且参数delay为零,即这里的execute、submit用于提交立即执行的非周期性任务,这与ExecutorService接口定义相符。
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
public Future<?> submit(Runnable task) {
return schedule(task, 0, NANOSECONDS);
}
public <T> Future<T> submit(Runnable task, T result) {
return schedule(Executors.callable(task, result), 0, NANOSECONDS);
}
public <T> Future<T> submit(Callable<T> task) {
return schedule(task, 0, NANOSECONDS);
}
3) reExecutePeriodic
reExecutePeriodic((RunnableScheduledFuture)
方法用于周期性任务的重新入队。
主要逻辑:①若当前状态可执行任务,就将任务入队。在任务入队后,②若当前状态不可执行任务且从队列中成功移除任务,则取消此任务。③反之则(当有效线程数小于corePoolSize)启动新Worker线程(此线程会等待任务队列中任务出队,然后执行任务) 。
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();//父类中的方法
}
}
4) 成员变量的setter/getter方法
set/getContinueExistingPeriodicTasksAfterShutdownPolicy用于设置/获取周期性任务在shutdown之后是否应继续执行的布尔值。
set/getExecuteExistingDelayedTasksAfterShutdownPolicy用于设置/获取非周期性任务在shutdown之后是否应执行的布尔值。
set/getRemoveOnCancelPolicy用于设置/获取任务被取消后是否应从队列中移除的布尔值。
getQueue用于获取任务队列。
public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) {
continueExistingPeriodicTasksAfterShutdown = value;
if (!value && isShutdown())//执行器关闭后不能继续执行任务,就调用onShutdown。
onShutdown();
}
public boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() {
return continueExistingPeriodicTasksAfterShutdown;
}
public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) {
executeExistingDelayedTasksAfterShutdown = value;
if (!value && isShutdown())//执行器关闭后不能继续执行任务,就调用onShutdown。
onShutdown();
}
public boolean getExecuteExistingDelayedTasksAfterShutdownPolicy() {
return executeExistingDelayedTasksAfterShutdown;
}
public void setRemoveOnCancelPolicy(boolean value) {
removeOnCancel = value;
}
public boolean getRemoveOnCancelPolicy() {
return removeOnCancel;
}
public BlockingQueue<Runnable> getQueue() {
return super.getQueue();
}
5) shutdown系列方法
shutdown()
与shutdownNow()
均直接调用父类相应方法实现,没有自己的其他逻辑。
public void shutdown() {
super.shutdown();
}
public List<Runnable> shutdownNow() {
return super.shutdownNow();
}
6) onShutdown
onShutdown()是一个钩子函数,它会被父类的shutdown()
方法回调。
onShutdown主要的逻辑:在执行器关闭后,将不可继续执行的任务从任务队列中移除、并取消此任务。
@Override 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();//父类的方法,尝试终止执行器
}
7) triggerTime系列方法
triggerTime方法用于计算下次执行任务的具体绝对时间。如果延时超过Long.MAX_VALUE,overflowFree方法会将延时限制在Long.MAX_VALUE范围内。
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}
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;
}
6 与父类ThreadPoolExecutor的对比
①SheduleThreadPoolExecutor只使用无界阻塞队列,此队列永远不满,它不会创建非核心线程,它的成员变量maximumPoolSize、keepAliveTime没有意义。
而ThreadPoolExecutor可以自己配置阻塞队列的类型,它的成员变量maximumPoolSize、keepAliveTime有相当大的作用。
②SheduleThreadPoolExecutor只能将任务直接放入队列中,如果有需要的话就启动新线程。但新启动的线程不会立即执行任务(因为延时任务只能在延时过后执行任务),它必须到任务队列中等待任务出队。
而ThreadPoolExecutor在效线程数小于corePoolSize或队列已满的情况下,新启动的线程会立即执行任务。
③SheduleThreadPoolExecutor对于周期性任务,任务被执行一次后需要修改下次执行的时间并将此任务重新放回队列。
而在ThreadPoolExecutor中任务只会被执行一次,在任务出队后不会再放回队列中。
④ThreadPoolExecutor在被关闭后(只是在shutdown()而不是shutdownNow()之后) ,任务队列中的等待任务或正在被执行的任务将能继续执行下去。而SheduleThreadPoolExecutor可以通过相关方法改变“执行器被关闭后,任务是否继续执行”的策略。