ScheduledThreadPoolExecutor阅读笔记
一、简介
- 指定某个时刻执行任务,是通过延时队列的特性来解决的
- 重复执行,是通过在任务执行后再次把任务加入到队列来解决的
二、继承关系图
三、存储结构
- 自己内部封装了一个ScheduledFutureTask内部类,用于封装FutureTask的
- 自己内部封装了一个DelayedWorkQueue内部类,延迟阻塞队列,用于等待线程
四、源码分析
内部类
- ScheduledFutureTask内部类继承图
-
ScheduledFutureTask 内部类主要方法
public void run() { // 是否重复执行 boolean periodic = isPeriodic(); // 检查线程池状态,如果状态不可运行,就取消 if (!canRunInCurrentRunState(periodic)) cancel(false); // 如果不是重复运行,也就是一次任务,则直接调用父类FutureTask.run()方法 else if (!periodic) ScheduledFutureTask.super.run(); // else if (ScheduledFutureTask.super.runAndReset()) { // 设置下次执行的时间,也就是修改period属性 setNextRunTime(); // 重复执行,直接主类 reExecutePeriodic(outerTask); } } //ScheduledThreadPoolExecutor#reExecutorPeriodic 重复执行 void reExecutePeriodic(RunnableScheduledFuture<?> task) { // 调用重复执行,那么肯定是重复任务 // 检查线程池状态 if (canRunInCurrentRunState(true)) { // 再次把任务扔到任务队列中 super.getQueue().add(task); // 再次检查线程池状态, // 如果状态不可用就从队列删除任务,删除成功后取消线程(不是强制取消) if (!canRunInCurrentRunState(true) && remove(task)) task.cancel(false); else // 保证工作线程足够 ensurePrestart(); } }
-
DelayedWorkQueue内部类继承关系图
-
DelayedWorkQueue 内部类主要函数
-
take
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); // 如果小于等于0,说明这个任务到时间了,可以从队列出队了 if (delay <= 0) // 出队,然后堆化 return finishPoll(first); // 还没有到时间 first = null; // don't retain ref while waiting // 如果前面有线程在等待,直接进入等待 if (leader != null) available.await(); else { // 前面没有等待,直接把当前线程做为leader Thread thisThread = Thread.currentThread(); leader = thisThread; try { // 等待上面计算的延时时间,再自动唤醒 available.awaitNanos(delay); } finally { // 唤醒后再次获得锁后把leader置空(不然重新循环会加入队列) if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && queue[0] != null) // 相当于唤醒下一个等待的任务 available.signal(); // 释放锁 lock.unlock(); } }
-
属性
构造
- 其实就是用的ThreadPoolExecutor的构造,只是等待队列采用的自己的DelayedWorkDueue内部类
主要方法
-
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内部类对象 ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period)); // 钩子方法,给子类用来替换装饰task,其实还是sft // 就是子类转父类而已。。 RunnableScheduledFuture<Void> t = decorateTask(command, sft); sft.outerTask = t; // 延迟执行任务 delayedExecute(t); return t; }
-
delayedExecute
private void delayedExecute(RunnableScheduledFuture<?> task) { // 判断任务是否已关闭 if (isShutdown()) reject(task); else { // 入队,这个就是ThreadPoolExecutor#wrokQueue任务队列 // add 失败会抛出异常,队列满 super.getQueue().add(task); // 再次检查线程池 if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task)) task.cancel(false); else // 保证空间足够让线程执行任务 ensurePrestart(); } } void ensurePrestart() { int wc = workerCountOf(ctl.get()); if (wc < corePoolSize) // 创建工作线程(也就是等待队列) // 注意,这里没有传入firstTask采纳数,因为上面已经把线程扔入了队列 // 同时,没有用maxPoolSize参数,所以最大线程参数在队列中是没有使用的 addWorker(null, true); else if (wc == 0) addWorker(null, false); }
使用Demo
public class ThreadPoolTest03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个定时线程池
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5);
System.out.println("start: " + System.currentTimeMillis());
// 执行一个无返回值任务,5秒后执行,只执行一次
scheduledThreadPoolExecutor.schedule(() -> {
System.out.println("spring: " + System.currentTimeMillis());
}, 5, TimeUnit.SECONDS);
// 执行一个有返回值任务,5秒后执行,只执行一次
ScheduledFuture<String> future = scheduledThreadPoolExecutor.schedule(() -> {
System.out.println("inner summer: " + System.currentTimeMillis());
return "outer summer: ";
}, 5, TimeUnit.SECONDS);
// 获取返回值
System.out.println(future.get() + System.currentTimeMillis());
// 按固定频率执行一个任务,每2秒执行一次,1秒后执行
// 任务开始时的2秒后
scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
System.out.println("autumn: " + System.currentTimeMillis());
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
}, 1, 2, TimeUnit.SECONDS);
// 按固定延时执行一个任务,每延时2秒执行一次,1秒执行
// 任务结束时的2秒后,本文由公从号“彤哥读源码”原创
scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {
System.out.println("winter: " + System.currentTimeMillis());
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
}, 1, 2, TimeUnit.SECONDS);
}
}