ScheduledThreadPoolExecutor
这是一个执行延迟任务或者周期性任务的线程池
ScheduledThreadPoolExecutor的三个参数
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
- corePoolSize:核心工作线程数
- threadFactory:线程工厂
- handler:拒绝策略
ScheduledThreadPoolExecutor默认使用的是DelayedWorkQueue队列,这是基于DelayedQueue实现的延迟队列,而且是无界队列。
ScheduledThreadPoolExecutor的核心属性
// 这些属性是任务取消时需要使用
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
private volatile boolean removeOnCancel = false;
// 计数器。如果两个任务执行时间一模一样,用这个来判断谁先执行
private static final AtomicLong sequencer = new AtomicLong();
ScheduledFutureTask内部类:
// 定时任务线程池可以传Runable任务,也可以传Callable任务
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
// 如果两个任务执行时间相同,用这个属性判断谁先执行
private final long sequenceNumber;
// 任务执行时间
private long time;
// period==0:代表此任务只执行一次
// period>0:代表At,任务的间隔时间=周期时间
// period<0:代表With,任务的间隔时间=任务执行时间+周期时间
private final long period;
// 封装的周期性执行的任务,通过此属性拿到任务放回阻塞队列
RunnableScheduledFuture<V> outerTask = this;
int heapIndex;
// 构建延迟执行的任务
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
// 构建At或者With任务
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
this.sequenceNumber = sequencer.getAndIncrement();
}
// 此处省略其他方法
}
ScheduledThreadPoolExecutor的核心方法
execute方法
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
execute方法中调用的是schedule方法,延迟时间默认为0,所以任务会立即执行
schedule方法
- 如果任务为null或者时间单位为null,抛出异常
- 将任务执行时间计算出来
- 将任务封装成延迟执行任务
- 执行delayedExecute方法
// command:待执行的任务;delay:延迟时间;unit:时间单位
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
// 任务为空,或者时间单位为空,抛出空指针异常
throw new NullPointerException();
// 将任务封装成RunnableScheduledFuture
// triggerTime方法是将任务执行时间计算出来,并且对时间做合理性校验
// ScheduledFutureTask构造方法将任务封装成延迟执行的任务
// decorateTask方法的入参是原任务和封装好的任务
// 但是此方法默认直接将封装好的任务返回,程序猿可以重写此方法
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
// 执行任务
delayedExecute(t);
return t;
}
triggerTime方法:
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));
}
ScheduledFutureTask方法:
ScheduledFutureTask(Runnable r, V result, long ns) {
// 因为传入的任务时Runnable,所以返回值result为null
super(r, result);
this.time = ns;
// period为0说明是延迟任务
this.period = 0;
// 如果两个任务的执行时间相同,需要根据sequenceNumber判断谁先执行
this.sequenceNumber = sequencer.getAndIncrement();
}
delayedExecute方法:
- 如果线程池状态是SHUTDOWN,执行拒绝策略
- 将任务放入延迟队列中
- 如果线程池状态是SHUTDOWN,并且在SHUTDOWN状态下,延迟任务不能执行,删除任务
- 否则执行ensurePrestart方法
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
// 线程池状态不为RUNNING,执行拒绝策略
reject(task);
else {
// 将任务放入延迟队列
super.getQueue().add(task);
// 任务放到延迟队列中后,再次判断线程池状态是否为SHUTDOWN
// 并且判断在SHUTDOWN状态下,队列中的延迟任务是否可以执行(默认情况下可以执行)
if (isShutdown() &&
// 默认情况下,SHUTDOWN状态下的延迟任务可以执行
!canRunInCurrentRunState(task.isPeriodic()) &&
// 如果延迟任务不能执行,删除任务
remove(task))
task.cancel(false);
else
// 执行任务
ensurePrestart();
}
}
isPeriodic方法:
// 任务是延迟任务,返回false
// 任务是周期任务,返回true
public boolean isPeriodic() {
return period != 0;
}
canRunInCurrentRunState方法:
boolean canRunInCurrentRunState(boolean periodic) {
return isRunningOrShutdown(periodic ?
// continueExistingPeriodicTasksAfterShutdown值默认为false
// executeExistingDelayedTasksAfterShutdown值默认为true
// 说明默认情况下,在线程池为SHUTDOWN状态下
// 周期任务不能执行,延迟任务可以执行
continueExistingPeriodicTasksAfterShutdown :
executeExistingDelayedTasksAfterShutdown);
}
isRunningOrShutdown方法:
final boolean isRunningOrShutdown(boolean shutdownOK) {
// 获取线程池状态
int rs = runStateOf(ctl.get());
// 如果线程池状态是RUNNING,返回true
// 如果线程池状态是SHUTDOWN,根据shutdownOK返回
return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
}
ensurePrestart方法:
- 获取工作线程数
- 如果工作线程数小于最大核心工作线程数,创建核心工作线程执行任务
- 如果最大核心工作线程设置为0,创建普通工作线程执行任务
void ensurePrestart() {
// 获得工作线程数
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
// 工作线程数小于核心工作线程最大数
// 创建核心工作线程执行任务
addWorker(null, true);
else if (wc == 0)
// 工作线程数为0,在不满足wc < corePoolSize条件的情况下
// 说明核心工作线程最大数也设置为0
// 创建普通工作线程执行任务
addWorker(null, false);
}
scheduleAtFixedRate方法
scheduleAtFixedRate方法和scheduleWithFixedDelay方法在源码层面的区别就是计算周期时间时,传递给period属性的值一个是正数,一个是负数
- 如果任务为null,或者时间单位为null,抛出异常
- 如果周期时间小于0,抛出异常
- 将任务封装成周期任务
- 执行delayedExecute方法
// command:任务;initialDelay:第一次执行的延迟时间;period:周期时间;unit:时间单位
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
// period大于0才是At任务,小于和等于0,直接抛异常
throw new IllegalArgumentException();
// 将任务封装成ScheduledFutureTask
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
// decorateTask方法返回封装后的任务,程序猿可以重写此方法
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
// 周期任务在执行完后需要重新放回队列,将任务赋值给outerTask属性
// 是为了重新拿任务时方便
sft.outerTask = t;
// 处理任务
delayedExecute(t);
return t;
}
无论是延迟任务还是周期任务,执行时都会调用ScheduledFutureTask的run方法
- 判断任务在当前线程池状态下是否可以执行
- 如果不能执行,取消任务
- 如果可以执行,延迟任务执行后,不做任何操作
- 周期任务执行后,计算出下次执行的时间,然后将任务放回队列
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
// 判断任务在线程池当前状态下是否可以执行
cancel(false);
else if (!periodic)
// 任务为延迟执行任务,执行任务后什么也不用做
ScheduledFutureTask.super.run();
// runAndReset方法是执行任务的方法
else if (ScheduledFutureTask.super.runAndReset()) {
// 周期任务执行后,需要计算下次执行的时间
setNextRunTime();
// 将任务放回队列中
reExecutePeriodic(outerTask);
}
}
setNextRunTime方法:
private void setNextRunTime() {
long p = period;
if (p > 0)
// At任务,下次执行时间=任务上次开始执行时间+间隔时间
// 如果任务执行时间>间隔时间,那么下次任务执行需等此次任务执行后立即执行
time += p;
else
// With任务,下次执行时间=任务执行时间+间隔时间
time = triggerTime(-p);
}