ScheduledThreadPoolExecutor
推理: 由于ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,故拥有其特性及功能,但是目前想要实现调度功能,需要加入自己的逻辑实现
需求 : 需要进行任务调度且可以延迟执行
结论 : 需要能够对任务进行时间排队的任务队列
问题 : 如何做到调度与延迟?
- DelayedWorkQueue 延迟工作队列
1. 构造方法
- 默认构造方法
public ScheduledThreadPoolExecutor(int corePoolSize) {
//调用父类构造实现创建
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
- 图解
- 描述
- 由于ScheduleThreadPoolExecutor使用了父类ThreadPoolExecutor的构造方法,传参中的任务队列为当前内部类,且并无初始化大小及DelayWorkQueue的构造方法,所以DelayedWorkQueue为无界队列,即没有容量限制
- 由于非核心线程的创建的前提条件是核心线程数与任务队列满载,所以ScheduleThreadExecutor的构造函数中除去核心线程数及任务队列其他参数无效
2. 执行方式
/**
* @param Runnable command 待执行任务
* @param long delay 延迟时间
* @param TimeUnit unit delay的时间单位
*/
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
if (command == null || unit == null){
throw new NullPointerException();
}
//hock钩子函数,用于扩展
//decorateTask()方法默认实现为空,仅返回传入的任务,留给用户重写,如字面意思,可以对任务进行操作加工等
//ScheduledFutureTask类,解析如下图及代码详解
RunnableScheduledFuture<?> t = decorateTask(command,new ScheduledFutureTask<Void>(command, null,triggerTime(delay, unit)));
//执行延时任务方法
delayedExecute(t);
//返回任务
return t;
}
- 相关图解
-
描述
- 由于接口在设计过程中需要满足单一原则,即接口隔离,如果想同时拥有两个或者两个以上接口的能力,那么需要继承对应接口,从而完成接口(能力)的组合
-
ScheduledFutureTask 代码详解
private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
//序列号,依据任务放入顺序严格递增
private final long sequenceNumber;
//超时时间,绝对时间
private long time;
//周期,每个多长时间执行一次,即 下次任务执行时间 = 绝对时间 + 周期
private final long period;
RunnableScheduledFuture<V> outerTask = this;
//索引,任务的存放是以小顶堆(二叉树)的形式存储,所以,该下标即是表示的当前任务在数组中的位置
int heapIndex
//直接使用父类构造创建一次性任务
ScheduledFutureTask(Runnable r, V result, long ns) {
this(r, result,ns,0);
}
//基于父类构造方法基础上创建周期性任务
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
super(r, result);
this.time = ns;
this.period = period;
//利用AtomicLong原子类保证序列号的正常使用,即保证多线程下序列号的安全使用
this.sequenceNumber = sequencer.getAndIncrement();
}
//基于父类构造的Callable方式创建一次性任务
ScheduledFutureTask(Callable<V> callable, long ns) {
super(callable);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
//获取任务的剩余延迟时间,剩余时间 = 任务开始时间 - 当前时间; (纳秒)
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
//由于RunnableScheduledFuture继承ScheduledFuture继承Delayed继承Comparable故重写方法以便比较
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;
}
//是否为周期性任务判断
public boolean isPeriodic() {
return period != 0;
}
//设置下一次运行定期任务的时间。(详解见文章下面setNextRunTime()方法图解)
private void setNextRunTime() {
long p = period;
if (p > 0)
//schedule相关,
time += p;
else{
//scheduleWithFixedDelay相关
time = triggerTime(-p);
}
}
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = super.cancel(mayInterruptIfRunning);
if (cancelled && removeOnCancel && heapIndex >= 0)
remove(this);
return cancelled;
}
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);
}
}
}
- setNextRunTime() 方法详解
private void setNextRunTime() { long p = period; if (p > 0){ time += p; } //下次执行时间 = 当前时间 + 延迟时间 else{ time = triggerTime(-p); } //下次执行时间 = 当前时间 + 任务消耗时间 + 延迟时间 }
- reExecutePeriodic() →→→ 任务定期重新执行
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
//判断线程池状态
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
//判断线程池状态,如果不满足运行可运行条件,则删除本次任务
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
//保证线程池至少有一个线程
ensurePrestart();
}
}
频繁的判断线程池状态,是由于多线程状态下,线程池的运行随时可能被其他线程锁中断