前言

它是一种可以安排在给定的延迟之后执行一次或周期性执行任务的ThreadPoolExecutor。因为它继承了ThreadPoolExecutor, 当然也具有处理普通Runnable、Callable 任务的能力,当需要多个工作线程辅助时,或者当需要 ThreadPoolExecutor 具有额外的灵活性或功能(该类扩展的这些功能)时,这个类通常比Timer更好。

延迟或周期任务一旦延迟/周期时间到达变得可执行时,它们具体在何时会被线程池调度执行是无法实时保障的,因为同一时期可能有多个任务都需要被执行,该类会按照先进先出FIFO的原则调度执行那些被安排在同一执行时间的任务。

当提交的任务在执行之前被取消,则不会再被执行,但是默认情况下,被取消的任务不会自动从工作队列中被删除,直到它的延迟或者周期时间到达被线程池调度的时候才会从任务队列中被取出,发现已经取消则放弃执行并且不安排周期任务下一次执行,为了避免这种情况,可以调用setRemoveOnCancelPolicy(true)来启用被取消的任务立即从工作队列中删除的特性,默认不开启此特性。

通过scheduleAtFixedRate或scheduleWithFixedDelay提交的周期任务的连续执行不会重叠(即由于某次执行时间较长,前后两次执行不会同时进行)。虽然每一次的执行都是由不同的线程来执行,但是前一次执行的效果会 happen-before 后续执行的效果。周期任务一旦某一次执行任务抛出异常将取消后续的周期执行。

虽然此类继承了ThreadPoolExecutor,但使用了一个无界的任务队列(延迟工作队列DelayedWorkQueue),因此对maximumPoolSize的调整将对此类没有任何作用。此外,将corePoolSize设置为零或使用allowCoreThreadTimeOut几乎从来都不是一个好主意,因为这可能会使线程池在需要运行任务时没有线程来处理这些任务。

扩展注意事项,该类重写了execute和submit方法来生成内部ScheduledFuture对象,以控制每个任务的延迟和调度。为了保持功能,子类中对这些方法的任何进一步重写都必须依赖父类的版本,这将有效地避免自定义额外的任务类型。但是该类提供了另一种可被重写的方法decorateTask (Runnable和Callable各有一个版本),可定制用于通过execute、submit、schedule、scheduleAtFixedRate和scheduleWithFixedDelay方法传入的任务的具体类型,默认情况下,ScheduledThreadPoolExecutor使用一个扩展了FutureTask、RunnableScheduledFuture的任务类型ScheduledFutureTask。但是,可以使用下列形式的子类修改或替换该类型:

 public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor {

     //自定义RunnableScheduledFuture
static class CustomTask<V> implements RunnableScheduledFuture<V> { ... } //重写decorateTask,返回自定义的RunnableScheduledFuture
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable r, RunnableScheduledFuture<V> task) {
return new CustomTask<V>(r, task);
} protected <V> RunnableScheduledFuture<V> decorateTask(
Callable<V> c, RunnableScheduledFuture<V> task) {
return new CustomTask<V>(c, task);
}
// ... add constructors, etc.
}

源码解读

属性字段

 public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService{

 //是否在线程池shutdown之后继续执行周期任务,为false时,将会在线程池shutdown时取消/废弃周期任务
private volatile boolean continueExistingPeriodicTasksAfterShutdown; //是否在线程池shutdown之后继续执行延迟任务,为false时,将会在线程池shutdown时取消非周期任务(即取消延迟任务)
private volatile boolean executeExistingDelayedTasksAfterShutdown = true; //为true时,将会在取消任务时立即从任务队列中删除该任务
private volatile boolean removeOnCancel = false; //保证任务的先进先出的序列
private static final AtomicLong sequencer = new AtomicLong(); //返回当前纳秒时间。
final long now() {
return System.nanoTime();
}

他的属性字段只有四个,continueExistingPeriodicTasksAfterShutdown,executeExistingDelayedTasksAfterShutdown用于指示当线程池shutdown之后(仅限线程池处于SHUTDOWN状态的时候)是否继续执行周期/延迟任务,它们并不能阻止线程池即将被终结的命运。removeOnCancel指示是否在任务被取消后立即从任务队列中移除。sequencer用于记录任务进入工作队列的顺序,以便将来两个任务撞到同一时间需要被执行时,决定谁先谁后的问题。

虽然 continueExistingPeriodicTasksAfterShutdown会导致即使线程池处于SHUTDOWN状态也会继续将周期任务加入到线程池队列等待下一次被调度执行,但一旦线程池调度之后发现任务队列为空,线程池依然会走向终结,因为最后一个周期任务从队列中取走执行的时候,还没来得及将周期任务再次添加到任务队列,线程池可能就因为发现任务队列为空,从SHUTDOWN过渡到STOP或者TIDYING或者TERMINATED状态了,这个时候即使任务已经进入了任务队列也不会被调度执行。这两个参数仅仅可以使处于SHUTDOWN状态的线程池可以继续执行延迟或周期任务,但线程池从SHUTDOWN状态过渡到后面的状态是自动进行的可能就是瞬息之间,并不能保证这两个参数就一定会发挥作用,我觉得没什么用处显得很鸡肋。

RunnableScheduledFuture实现--- ScheduledFutureTask

ThreadPoolExecutor默认将Runnable、Callable的任务封装成了FutureTask的任务调度执行,该类为了实现任务的延迟和周期调度执行扩展了默认的FutureTask并实现了RunnableScheduledFuture:

 private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> { /** FIFO的序列号 */
private final long sequenceNumber; /** 以纳秒为单位的下一次任务执行的时间 */
private long time; //以纳秒为单位的重复任务的周期:
//正数表示固定频率的周期任务(scheduleAtFixedRate),
//负数表示固定延迟的周期任务(scheduleWithFixedDelay)。
//0表示不是一个重复执行的周期任务。
private final long period; /** 被周期执行的任务,指向的自身 */
RunnableScheduledFuture<V> outerTask = this; /**
* 任务在延迟数组中的索引,以便快速查找、取消任务.
*/
int heapIndex; //创建一个基于给定纳秒数延迟的一次性Runnable任务
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
} //创建一个基于给定纳秒数延迟和周期的周期性Runnable任务
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
this.sequenceNumber = sequencer.getAndIncrement();
} //创建一个基于给定纳秒数延迟的一次性Callable任务
ScheduledFutureTask(Callable<V> callable, long ns) {
super(callable);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
} //Delayed接口实现,返回值小于等于0时表示延迟已经结束
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
} //判断两个任务的先后执行顺序
public int compareTo(Delayed other) {
if (other == this) // 同一个对象
return 0;
if (other instanceof ScheduledFutureTask) { //是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) //按FIFO决定顺序
return -1;
else
return 1;
}
//非ScheduledFutureTask,即其它类型的延迟任务,借助getDelay比较
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
} //实现RunnableScheduledFuture接口,如果是周期重复执行的任务返回true
public boolean isPeriodic() {
return period != 0; //只要不是0,就是一个周期执行的任务
} //设置周期任务下一次的运行时间。
private void setNextRunTime() {
long p = period;
if (p > 0) //固定速率 fixed-rate:time,time + p,time +2*p,time + 3*p......
time += p;
else //固定延迟 fixed-delay: now + |p| 即当前系统时间 加周期时间
time = triggerTime(-p); //这是p是负数,所以取反传递的是正数
} //取消任务,重写FutureTask是为了实现实时将被取消的任务从工作队列中移除
public boolean cancel(boolean mayInterruptIfRunning) {
//直接使用FutureTask的cancel
boolean cancelled = super.cancel(mayInterruptIfRunning);
//取消任务之后,如果removeOnCancel为true,并且任务还存在延迟队列中(heapIndex >=0)
if (cancelled && removeOnCancel && heapIndex >= 0)
remove(this); //将被取消的任务从工作队列中移除
return cancelled;
} //重写 FutureTask的run方法,是为了将需要周期执行的任务重置/再次入队。
public void run() {
boolean periodic = isPeriodic();
//如果线程池的状态至少是STOP,或者是SHUTDOWN但是参数指定不在shutdown状态继续执行任务,则取消任务
if (!canRunInCurrentRunState(periodic))
cancel(false);
//不是周期任务,直接执行FutureTask.run
else if (!periodic)
ScheduledFutureTask.super.run();
//是周期执行的任务,
else if (ScheduledFutureTask.super.runAndReset()) { //任务正常结束
setNextRunTime(); //设置周期任务的下次执行时间
reExecutePeriodic(outerTask); //将当前任务再次加入线程池队列
}
}
}

ScheduledFutureTask就是被ScheduledThreadPoolExecutor线程池调度执行的任务实体,其成员属性中,sequenceNumber表示创建该任务的顺序,在构造方法中原子的自增。time是该任务下一次被调度执行的剩余延迟纳秒时长,period则是通过scheduleAtFixedRate和scheduleWithFixedDelay提交的周期任务的周期,非周期任务该字段为0。heapIndex是该任务在任务队列(平衡二叉堆实现的延迟队列DelayedWorkQueue)中的索引,方便快速查找和取消任务,如果任务已经从任务队列中移除,该值为-1。其构造方法与FutureTask一样支持Runnable(可以指定返回结果result)、Callable的任务,只是扩展多了延迟时间和周期时间参数。

该类实现的Delayed接口的getDelay方法以及compareTo是根据下一次任务执行的延迟剩余时间进行比较的,对于剩余延迟时间相同的根据sequenceNumber按先进先出FIFO的策略决定顺序。其run、cancel方法后面再说。

任务队列--DelayedWorkQueue

不像ThreadPoolExecutor那样可以支持任意阻塞队列作为任务队列,为了实现任务的延迟和周期执行,ScheduledThreadPoolExecutor只能使用内部实现的延迟任务队列DelayedWorkQueue,该类已经在前面的Java同步数据结构之DelayQueue/DelayedWorkQueue学习过了。其原理就是数组实现的平衡二叉堆,根据元素的延迟剩余时间多少分布成二叉堆,只有当延迟时间结束,元素才能被取出队列,并且取出的元素一定是当前队列中延迟时间最短的,这里的元素当然就是一个个ScheduledFutureTask任务实体,这里就不再对该内部类进行分析了,需要注意的是,DelayedWorkQueue在内部实现的时候,会将ScheduledFutureTask任务实体在数组中的索引记录到ScheduledFutureTask的heapIndex字段中,以方便快速查找和取消任务,并且当从任务队列中移除该任务时会将heapIndex置为-1.

构造方法

介绍完了ScheduledThreadPoolExecutor的内部字段与任务实体和任务队列这些基础设施,下面开始其构造方法的分析。这才是用户的使用入口。

 //创建具有给定核心线程数的ScheduledThreadPoolExecutor线程池
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
} //创建具有给定核心线程数和线程工厂的ScheduledThreadPoolExecutor线程池
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
} //创建具有给定核心线程数和拒绝策略的ScheduledThreadPoolExecutor线程池
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
} //创建具有给定核心线程数、线程工厂和拒绝策略的ScheduledThreadPoolExecutor线程池
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}

ScheduledThreadPoolExecutor有以上四个构造方法,分别只能对核心线程数corePoolSize、线程工厂和拒绝策略handler进行指定,其它参数都是内置的,maximumPoolSize都是Integer.MAX_VALUE,keepAliveTime都是0,任务队列都是DelayedWorkQueue。由于这里的任务队列DelayedWorkQueue是一个无界队列,因此对maximumPoolSize、keepAliveTime的任何设置与更新都是无任何意义的。

任务的提交

 // Override AbstractExecutorService methods
//普通非延迟非周期任务的提交----execute、submit
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
} // Override AbstractExecutorService methods /**
* @throws RejectedExecutionException
* @throws NullPointerException
*/
public Future<?> submit(Runnable task) {
return schedule(task, 0, NANOSECONDS);
} /**
* @throws RejectedExecutionException
* @throws NullPointerException
*/
public <T> Future<T> submit(Runnable task, T result) {
return schedule(Executors.callable(task, result), 0, NANOSECONDS);
} /**
* @throws RejectedExecutionException
* @throws NullPointerException
*/
public <T> Future<T> submit(Callable<T> task) {
return schedule(task, 0, NANOSECONDS);
} /**----------------------schedule实现------------------------**/
/**
* @throws RejectedExecutionException
* @throws NullPointerException
*/
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
//这里调用decorateTask方法是留给子类自定义RunnableScheduledFuture实现
//默认直接返回ScheduledFutureTask实例
RunnableScheduledFuture<?> t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
delayedExecute(t);
return t;
} /**
* @throws RejectedExecutionException
* @throws NullPointerException
*/
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
//这里调用decorateTask方法是留给子类自定义RunnableScheduledFuture实现
//默认直接返回ScheduledFutureTask实例
RunnableScheduledFuture<V> t = decorateTask(callable, new ScheduledFutureTask<V>(callable, triggerTime(delay, unit)));
delayedExecute(t);
return t;
} /*********************周期任务的提交***************************/ /**
* 提交固定频率的周期任务,
* 任务的执行时间分别是:initialDelay、initialDelay+period、initialDelay+2*period、initialDelay+3*period......
* @throws RejectedExecutionException
* @throws NullPointerException
* @throws IllegalArgumentException
*/
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));
//这里调用decorateTask方法是留给子类自定义RunnableScheduledFuture实现
//默认直接返回ScheduledFutureTask实例
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
} /**
* 提交固定延迟的周期任务,
* 即每一次周期任务都是上一次执行结束再延迟delay。
* @throws RejectedExecutionException
* @throws NullPointerException
* @throws IllegalArgumentException
*/
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));
//这里调用decorateTask方法是留给子类自定义RunnableScheduledFuture实现
//默认直接返回ScheduledFutureTask实例
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
} //入队一个任务,除非当前线程池状态不能接受该任务。
//与reExecutePeriodic不同的地方,若不接受任务,这里是走拒绝策略
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown()) //线程池状态至少是SHUTDOWN
reject(task); //走拒绝策略
else {
super.getQueue().add(task); //将任务加入队列
//线程池状态二次检查,如果不接受新任务,从任务队列中移除任务
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false); //执行ScheduledFutureTask的取消任务
else //否则确保线程池中至少启动有一个线程
ensurePrestart();
}
}

对于普通非延迟非周期任务通过execute、submit方法提交,这是重写了AbstractExecutorService的方法,统一由schedule方法调度执行,通过调用schedule时传递的delay参数可见,对于这类非延迟非周期的常规任务,延迟时间都是0,表示立即执行。

对于非周期延迟任务通过schedule方法实现,针对Callable、Runnable提供了两个重载方法。对于周期任务通过scheduleAtFixedRate、scheduleWithFixedDelay提交,scheduleAtFixedRate与scheduleWithFixedDelay的区别在线程池概述中已经交代了,简单来说,初始延迟为initialDelay,周期为了period的任务通过scheduleAtFixedRate提交的话,那么其以后的执行时间分别是:initialDelay、initialDelay + period、initialDelay + 2*period、initialDelay + 3*period 以此类推;而通过scheduleWithFixedDelay提交的话,那么每一次任务的执行都是根据上一次执行结束之后间隔period后开始执行。就算某一次的执行时间太长,以至于到达了通过scheduleAtFixedRate提交的任务的下一次的执行时间,线程池也不会让同一个任务同时执行的,因为任务的每一次执行都回将执行它的线程设置到它的runner字段中,一旦发现该字段不为null,就表示有线程在执行,从而跳过执行。

上面的任务提交过程中,都调用了一个decorateTask方法,该方法默认什么也不做,原样返回创建的ScheduledFutureTask实例,这是留给子类重写decorateTask可以创建自定义的RunnableScheduledFuture实现类。创建ScheduledFutureTask实例的时候会根据延迟时间用triggerTime方法计算出任务第一次被调度执行的剩余延迟时间。

从提交任务的源码可以发现,它们的真正核心代码是调用delayedExecute方法实现的,该方法的逻辑很简单:

如果线程池已经shutdown,则走拒绝策略;否则将任务加入任务队列,然后再次确认线程池的状态,如果线程池已经不接受任务的话需要将任务从队列中移除,并取消任务;成功加入队列然后创建一个核心线程,除非线程数量达到了corePoolSize限制。

任务的提交总的来说就是创建任务实体ScheduledFutureTask,并在线程池处于活动情况下加入到任务队列。

任务的执行

通过上一章ThreadPoolExecutor的分析,我们知道任务的执行,其实就是由线程池调度执行任务的run方法,这里就是ScheduledFutureTask的run方法。

 //重写 FutureTask的run方法,是为了将需要周期执行的任务重置/再次入队。
public void run() {
boolean periodic = isPeriodic();
//如果线程池的状态至少是STOP,或者是SHUTDOWN但是参数指定不在shutdown状态继续执行任务,则取消任务
if (!canRunInCurrentRunState(periodic))
cancel(false);
//不是周期任务,直接执行FutureTask.run
else if (!periodic)
ScheduledFutureTask.super.run();
//是周期执行的任务,
else if (ScheduledFutureTask.super.runAndReset()) { //任务正常结束
setNextRunTime(); //设置周期任务的下次执行时间
reExecutePeriodic(outerTask); //将当前任务再次加入线程池队列
}
} //设置周期任务下一次的运行时间。
private void setNextRunTime() {
long p = period;
if (p > 0) //固定速率 fixed-rate:time,time + p,time +2*p,time + 3*p......
time += p;
else //固定延迟 fixed-delay: now + |p| 即当前系统时间 加周期时间
time = triggerTime(-p); //这是p是负数,所以取反传递的是正数
} //确定是否可以在当前线程池的状态继续执行周期/延迟任务
boolean canRunInCurrentRunState(boolean periodic) {
return isRunningOrShutdown(periodic ?
continueExistingPeriodicTasksAfterShutdown :
executeExistingDelayedTasksAfterShutdown);
} //ScheduledThreadPoolExecutor需要的状态检查,以便在线程池shutdown期间继续执行周期/延迟任务
//一旦线程池的状态超过SHUTDOWN,即STOP,TIDYING,TERMINATED则无论如何都返回false。
final boolean isRunningOrShutdown(boolean shutdownOK) {
int rs = runStateOf(ctl.get());
return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
} //重新人队一个周期性执行的任务,除非当前线程池状态不能接受该任务。
//与delayedExecute的思想相同,不同的是如果不接受任务,这里是直接删除任务,而delayedExecute是拒绝任务
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
//如果线程池的当前状态或者相关参数指示可以继续接收任务
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task); //将任务加入队列
//线程池状态二次检查,如果不接受任务,从任务队列中移除任务
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false); //执行ScheduledFutureTask的取消任务
else //否则确保线程池中至少启动有一个线程
ensurePrestart();
}
} /**
* 返回一个延迟任务的下一次触发时间
*/
private long triggerTime(long delay, TimeUnit unit) {
//转换成纳秒调用triggerTime(long)
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
} //返回一个延迟任务的下一次触发时间,delay是纳秒
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
} //限制队列中的所有任务的延迟都在Long.MAX_VALUE范围内,避免 compareTo 方法溢出。
//如果一个任务满足出队的条件,但是还没有出队,并且添加了其它一些延迟为Long.MAX_VALUE的任务,则会发生这种情况
private long overflowFree(long delay) {
Delayed head = (Delayed) super.getQueue().peek(); //拿到当前队列中剩余延迟最小的
if (head != null) { //队列不为空
long headDelay = head.getDelay(NANOSECONDS);
//这里delay是一个正数,为什么减一个负数还小于0呢,只能是溢出了Long.MAX_VALUE范围
if (headDelay < 0 && (delay - headDelay < 0))
delay = Long.MAX_VALUE + headDelay; //实际上就是 Long.MAX_VALUE 减了一个正数,因此保证delay小于Long.MAX_VALUE
}
return delay;
}

这里的run、setNextRunTime都是ScheduledFutureTask的方法,其它方法都是ScheduledThreadPoolExecutor的方法,为了方法阅读将它们全部贴出来了。ScheduledFutureTask的run方法也很简单:

如果线程池的状态不能安排任务执行了,则调用cancel(false)取消任务。

如果不是周期任务,即延迟任务(包括延迟为0的普通任务),直接调用FutureTask的run方法。该方法在线程池概述中已经分析了,就不再详述,它会执行任务并设置任务的结果。

如果是周期执行的任务,则执行FutureTask的runAndReset方法,该方法不设置任务的执行结果,因为周期任务本来就没有返回结果一说,如果任务执行过程中没有抛出异常,则通过setNextRunTime设置任务下一次执行的延迟剩余时间,然后通过reExecutePeriodic再次将任务加入任务队列,当然如果线程池此时已经shutdown即不能安排任务执行的话,只有取消任务了,往后的周期也就不会再执行了。

通过任务被调度的执行过程,可见非周期任务的执行与ThreadPoolExecutor一样都是调用FutureTask的run方法,因此可以通过提交任务时返回的Future获取异步任务的结果,而周期任务是通过不设置执行结果的runAndReset方法执行的,因此并没有结果返回,如果通过提交周期任务返回的ScheduledFuture调用get想获取结果将会永久阻塞。

另外对于周期任务,如果任务的执行过程中抛出了异常,将取消以后的周期执行,因此提交的周期任务的run方法中应该将所有的异常都捕获,否则一旦抛出异常,周期任务就停止了。

任务的取消

cancel方法也是由ScheduledFutureTask中实现的,其是Future的接口方法,为了方便任务提交者可以通过返回的Future实例取消任务的执行:

 //取消任务,重写FutureTask是为了实现实时将被取消的任务从工作队列中移除
public boolean cancel(boolean mayInterruptIfRunning) {
//直接使用FutureTask的cancel
boolean cancelled = super.cancel(mayInterruptIfRunning);
//取消任务之后,如果removeOnCancel为true,并且任务还存在延迟队列中(heapIndex >=0)
if (cancelled && removeOnCancel && heapIndex >= 0)
remove(this); //将被取消的任务从工作队列中移除
return cancelled;
}

可见,任务的取消实际还是调用的FutureTask的cancel实现的,ScheduledFutureTask重写该方法是为了实现取消任务的同时立即将任务从工作队列移除的特性。在ScheduledThreadPoolExecutor中调用cancel的时候传入的mayInterruptIfRunning都是false,因此这种线程池在取消任务时并不会中断已经处于执行中的任务。

对于非周期任务,如果任务还没被从任务队列取走执行,那么取消之后,任务将不会执行,如果任务已经在执行,则取消不会成功,更不会中断任务的执行。

对于周期任务,如果任务还没被从任务队列取走执行,那么取消之后,任务也将不会执行,如果任务已经在执行,则取消之后,由于其状态变成了CANCELLED,在runAndReset方法返回之前,发现任务的状态不是NEW,因此返回false,从而不再安排任务的下一次执行。

其它方法---onShutdown

在ThreadPoolExecutor的shutdown的方法中,线程池的状态被改变的SHUTDOWN之后,会回调一个钩子函数onShutdown,该方法在ThreadPoolExecutor中什么也没做,而在ScheduledThreadPoolExecutor中有了重写:

 //取消并清除由于关闭策略而不应该运行的任务队列中的所有任务。该方法在shutdown是回调
@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 {
// 遍历快照以避免迭代器异常
for (Object e : q.toArray()) {
if (e instanceof RunnableScheduledFuture) {
RunnableScheduledFuture<?> t =
(RunnableScheduledFuture<?>)e;
if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
t.isCancelled()) { // 如果已经取消依然从队列中移除任务
if (q.remove(t))
t.cancel(false); //没有取消的,则取消任务,但不中断正在执行的任务。
}
}
}
}
tryTerminate(); //尝试看能不能进一步将线程池的状态转到TIDYING、TERMINATED。
}

可见,在线程池状态转到SHUTDOWN之后,在调用tryTerminate使线程池走向真正终结之前,ScheduledThreadPoolExecutor会确保将已经入队的任务取消并移除。

总结

总的来说,ScheduledThreadPoolExecutor线程池的实现是在ThreadPoolExecutor的基础上进行的扩展,并借助了一个平衡二叉堆实现的延迟队列作为任务队列,因为如果理解了ThreadPoolExecutor和前面的Java同步数据结构之DelayQueue/DelayedWorkQueue的话,理解起来将没有任何困难。

ScheduledThreadPoolExecutor就是一种支持延迟和周期任务的线程池,当然它也支持提交普通的任务,把他当成一个ThreadPoolExecutor来使用,不过一般来说还是专门用于延迟和周期任务。需要注意的是,周期任务不支持返回结果,因此不要调用提交任务时返回的Future的get方法,而且周期任务在执行任务体时,如果抛出异常将导致周期性结束,之后不再会被执行,因此必要时需要将任务执行体的异常全部捕获。

Java并发包线程池之ScheduledThreadPoolExecutor的更多相关文章

  1. java并发包&线程池原理分析&锁的深度化

          java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...

  2. Java并发包——线程池

    Java并发包——线程池 摘要:本文主要学习了Java并发包中的线程池. 部分内容来自以下博客: https://www.cnblogs.com/dolphin0520/p/3932921.html ...

  3. Java并发包线程池之Executors、ExecutorCompletionService工具类

    前言 前面介绍了Java并发包提供的三种线程池,它们用处各不相同,接下来介绍一些工具类,对这三种线程池的使用. Executors Executors是JDK1.5就开始存在是一个线程池工具类,它定义 ...

  4. Java并发包线程池之ForkJoinPool即ForkJoin框架(一)

    前言 这是Java并发包提供的最后一个线程池实现,也是最复杂的一个线程池.针对这一部分的代码太复杂,由于目前理解有限,只做简单介绍.通常大家说的Fork/Join框架其实就是指由ForkJoinPoo ...

  5. Java并发包--线程池原理

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509954.html 线程池示例 在分析线程池之前,先看一个简单的线程池示例. 1 import jav ...

  6. Java并发包--线程池框架

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509903.html 线程池架构图 线程池的架构图如下: 1. Executor 它是"执行者 ...

  7. Java并发包线程池之ForkJoinPool即ForkJoin框架(二)

    前言 前面介绍了ForkJoinPool相关的两个类ForkJoinTask.ForkJoinWorkerThread,现在开始了解ForkJoinPool.ForkJoinPool也是实现了Exec ...

  8. Java并发包线程池之ThreadPoolExecutor

    参数详解 ExecutorService的最通用的线程池实现,ThreadPoolExecutor是一个支持通过配置一些参数达到满足不同使用场景的线程池实现,通常通过Executors的工厂方法进行配 ...

  9. Java 四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

    介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new T ...

随机推荐

  1. Springboot整合cxf后不能访问controller,不能访问接口

    参考版本 springboot 1.4.X <=========> cxf-spring-boot-starter-jaxws 3.1.X springboot 1.5.X <=== ...

  2. DNS服务——域名解析委派

    域名解析委派 域名解析委派和DNS域名解析递归查询很像,举个例子解释域名解析委派 ①假设在.net域名下有台计算机想要访问www.cac.com. ②.net这台DNS服务器不知道www.cac.co ...

  3. C实现哈希表

    1 哈希表原理 这里不讲高深理论,只说直观感受.哈希表的目的就是为了根据数据的部分内容(关键字),直接计算出存放完整数据的内存地址. 试想一下,如果从链表中根据关键字查找一个元素,那么就需要遍历才能得 ...

  4. windows 数据备份

    xcopy 数据目录   备份目录  /e /c /q /h /r /y at 00:00 /every:M,T,W,Th,F,S,Su d:\批处理文件名

  5. Nginx服务rewrite模块功能说明 网站自动跳转功能

    实现域名地址信息跳转,用于做伪静态地址 www.impkk.com/oldboy?edu.html 动态地址 www.impkk.com/oldboy-edu.html 伪静态地址 rewrite ^ ...

  6. 基于Java8的日期时间工具类DateTimeFormatter

    原文:https://blog.csdn.net/qq_36596145/article/details/85331002 import java.time.Instant; import java. ...

  7. TCP/IP结构图

    IP: TCP: UDP:

  8. test20181102 空间复杂度 和 test20181030 数独

    空间复杂度 考场做法 前有时间复杂度,后有空间复杂度. 但是这题不会有CE情况,所以较为好写. 就用map存复杂度,单层循环就搞定了. 至于判断维度的方法,我是用快读从字符串中读入. 然后不管常数,把 ...

  9. UVALive-7040-Color(容斥原理)

    链接: https://vjudge.net/problem/UVALive-7040 题意: Recently, Mr. Big recieved n owers from his fans. He ...

  10. java后台防止XSS的脚本攻击

    import java.util.regex.Pattern; //具体过滤关键字符public class XSSUtil { private static Pattern[] patterns = ...