@@ -6845,9 +6845,6 @@ FutureTask 类的成员方法:
68456845
68466846
68476847
6848- 参考视频:https://www.bilibili.com/video/BV13E411N7pp
6849-
6850-
68516848
68526849****
68536850
@@ -6912,8 +6909,8 @@ public ScheduledThreadPoolExecutor(int corePoolSize) {
69126909常用 API:
69136910
69146911* `ScheduledFuture<?> schedule(Runnable/Callable<V>, long delay, TimeUnit u)`:延迟执行任务
6915- * `ScheduledFuture<?> scheduleAtFixedRate(Runnable/Callable<V>, long initialDelay, long period, TimeUnit unit)`:定时执行任务 ,参数为初始延迟时间、间隔时间、单位
6916- * `ScheduledFuture<?> scheduleWithFixedDelay(Runnable/Callable<V>, long initialDelay, long delay, TimeUnit unit)`:定时执行任务 ,参数为初始延迟时间、间隔时间、单位
6912+ * `ScheduledFuture<?> scheduleAtFixedRate(Runnable/Callable<V>, long initialDelay, long period, TimeUnit unit)`:定时执行周期任务,不考虑执行的耗时 ,参数为初始延迟时间、间隔时间、单位
6913+ * `ScheduledFuture<?> scheduleWithFixedDelay(Runnable/Callable<V>, long initialDelay, long delay, TimeUnit unit)`:定时执行周期任务,考虑执行的耗时 ,参数为初始延迟时间、间隔时间、单位
69176914
69186915基本使用:
69196916
@@ -6983,7 +6980,7 @@ public ScheduledThreadPoolExecutor(int corePoolSize) {
69836980
69846981##### 成员变量
69856982
6986- * shutdown 后是否继续执行定时任务 :
6983+ * shutdown 后是否继续执行周期任务 :
69876984
69886985 ```java
69896986 private volatile boolean continueExistingPeriodicTasksAfterShutdown;
@@ -6998,10 +6995,11 @@ public ScheduledThreadPoolExecutor(int corePoolSize) {
69986995* 取消方法是否将该任务从队列中移除:
69996996
70006997 ```java
6998+ // 默认 false,不移除,等到线程拿到任务之后抛弃
70016999 private volatile boolean removeOnCancel = false;
70027000 ```
70037001
7004- * 任务的序列号:
7002+ * 任务的序列号,可以用来比较优先级 :
70057003
70067004 ```java
70077005 private static final AtomicLong sequencer = new AtomicLong();
@@ -7015,7 +7013,7 @@ public ScheduledThreadPoolExecutor(int corePoolSize) {
70157013
70167014##### 延迟任务
70177015
7018- ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,具有延迟执行的特点,覆盖 FutureTask 的 run 方法来实现对**延时执行、周期执行**的支持。对于延时任务调用 FutureTask#run,而对于周期性任务则调用 FutureTask#runAndReset 并且在成功之后根据 fixed-delay/fixed-rate 模式来设置下次执行时间并重新将任务塞到工作队列。
7016+ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,具有延迟执行的特点,覆盖 FutureTask 的 run 方法来实现对**延时执行、周期执行**的支持。对于延时任务调用 FutureTask#run,而对于周期性任务则调用 FutureTask#runAndReset 并且在成功之后根据 fixed-delay/fixed-rate 模式来设置下次执行时间并重新将任务塞到工作队列
70197017
70207018在调度线程池中无论是 runnable 还是 callable,无论是否需要延迟和定时,所有的任务都会被封装成 ScheduledFutureTask
70217019
@@ -7030,7 +7028,7 @@ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,
70307028* 执行时间:
70317029
70327030 ```java
7033- private long time; // 任务可以被执行的时间,以纳秒表示
7031+ private long time; // 任务可以被执行的时间,交付时间, 以纳秒表示
70347032 private final long period; // 0 表示非周期任务,正数表示 fixed-rate 模式的周期,负数表示 fixed-delay 模式
70357033 ```
70367034
@@ -7045,7 +7043,8 @@ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,
70457043* 任务在队列数组中的索引下标:
70467044
70477045 ```java
7048- int heapIndex; // -1 代表删除
7046+ // DelayedWorkQueue 底层使用的数据结构是最小堆,记录当前任务在堆中的索引,-1 代表删除
7047+ int heapIndex;
70497048 ```
70507049
70517050成员方法:
@@ -7066,13 +7065,40 @@ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,
70667065
70677066* compareTo():ScheduledFutureTask 根据执行时间 time 正序排列,如果执行时间相同,在按照序列号 sequenceNumber 正序排列,任务需要放入 DelayedWorkQueue,延迟队列中使用该方法按照从小到大进行排序
70687067
7068+ ```java
7069+ public int compareTo(Delayed other) {
7070+ if (other == this) // compare zero if same object
7071+ return 0;
7072+ if (other instanceof ScheduledFutureTask) {
7073+ // 类型强转
7074+ ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
7075+ // 比较者 - 被比较者的执行时间
7076+ long diff = time - x.time;
7077+ // 比较者先执行
7078+ if (diff < 0)
7079+ return -1;
7080+ // 被比较者先执行
7081+ else if (diff > 0)
7082+ return 1;
7083+ // 比较者的序列号小
7084+ else if (sequenceNumber < x.sequenceNumber)
7085+ return -1;
7086+ else
7087+ return 1;
7088+ }
7089+ // 不是 ScheduledFutureTask 类型时,根据延迟时间排序
7090+ long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
7091+ return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
7092+ }
7093+ ```
7094+
70697095* run():执行任务,非周期任务直接完成直接结束,**周期任务执行完后会设置下一次的执行时间,重新放入线程池的阻塞队列**,如果线程池中的线程数量少于核心线程,就会添加 Worker 开启新线程
70707096
70717097 ```java
70727098 public void run() {
70737099 // 是否周期性,就是判断 period 是否为 0
70747100 boolean periodic = isPeriodic();
7075- // 检查当前状态能否执行任务 ,不能执行就取消任务
7101+ // 根据是否是周期任务检查当前状态能否执行任务 ,不能执行就取消任务
70767102 if (!canRunInCurrentRunState(periodic))
70777103 cancel(false);
70787104 // 非周期任务,直接调用 FutureTask#run 执行
@@ -7088,7 +7114,7 @@ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,
70887114 }
70897115 ```
70907116
7091- 周期任务正常完成后任务的状态不会变化 ,依旧是 NEW,不会设置 outcome 属性。但是如果本次任务执行出现异常,会进入 setException 方法将任务状态置为异常,把异常保存在 outcome 中,方法返回 false,后续的该任务将不会再周期的执行
7117+ 周期任务正常完成后**任务的状态不会变化** ,依旧是 NEW,不会设置 outcome 属性。但是如果本次任务执行出现异常,会进入 setException 方法将任务状态置为异常,把异常保存在 outcome 中,方法返回 false,后续的该任务将不会再周期的执行
70927118
70937119 ```java
70947120 protected boolean runAndReset() {
@@ -7128,10 +7154,10 @@ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,
71287154 private void setNextRunTime() {
71297155 long p = period;
71307156 if (p > 0)
7131- // fixed-rate 模式,【时间设置为上一次执行任务的时间 +p】,两次任务执行的时间差
7157+ // fixed-rate 模式,【时间设置为上一次执行任务的时间 + p】,两次任务执行的时间差
71327158 time += p;
71337159 else
7134- // fixed-delay 模式,下一次执行时间是当【前这次任务结束的时间 (就是现在) +delay 值】
7160+ // fixed-delay 模式,下一次执行时间是【当前这次任务结束的时间 (就是现在) + delay 值】
71357161 time = triggerTime(-p);
71367162 }
71377163 ```
@@ -7144,7 +7170,8 @@ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,
71447170 if (canRunInCurrentRunState(true)) {
71457171 // 【放入任务队列】
71467172 super.getQueue().add(task);
7147- // 再次检查是否可以执行,如果不能执行且任务还在队列中未被取走,则取消任务
7173+ // 如果提交完任务之后,线程池状态变为了 shutdown 状态,需要再次检查是否可以执行,
7174+ // 如果不能执行且任务还在队列中未被取走,则取消任务
71487175 if (!canRunInCurrentRunState(true) && remove(task))
71497176 task.cancel(false);
71507177 else
@@ -7178,9 +7205,9 @@ ScheduledFutureTask 继承 FutureTask,实现 RunnableScheduledFuture 接口,
71787205
71797206##### 延迟队列
71807207
7181- DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先队列 PriorityQueue(小根堆)存储元素
7208+ DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先队列 PriorityQueue(小根堆、满二叉树 )存储元素
71827209
7183- 其他阻塞队列存储节点的数据结构大都是链表,**延迟队列是数组**,所以延迟队列出队头元素后需要让其他元素 (尾)替换到头节点,防止空指针异常
7210+ 其他阻塞队列存储节点的数据结构大都是链表,**延迟队列是数组**,所以延迟队列出队头元素后需要**让其他元素 (尾)替换到头节点** ,防止空指针异常
71847211
71857212成员变量:
71867213
@@ -7203,9 +7230,10 @@ DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先
72037230* 阻塞等待头节点的线程:
72047231
72057232 ```java
7206- // 通过阻塞方式去获取头结点,那么 leader 线程的等待时间为头结点的延迟时间,其它线程则会陷入阻塞状态
7207- // leader 线程获取到头结点后需要发送信号唤醒其它线程 available.asignAll()
7208- // 使用了 Leader/Follower 来避免不必要的等待,只让leader来等待需要等待的时间,其余线程无限等待直至被唤醒即可
7233+ // 线程池内的某个线程去 take() 获取任务时,如果延迟队列顶层节点不为null(队列内有任务),但是节点任务还不到触发时间,线程就去检查【队列的 leader】字段是否被占用
7234+ // * 如果未被占用,则当前线程占用该字段,然后当前线程到 available 条件队列指定超时时间(堆顶任务.time - now())挂起
7235+ // * 如果被占用,当前线程直接到 available 条件队列“不指定”超时时间的挂起
7236+ // leader 在 available 条件队列内是首元素,它超时之后会醒过来,然后再次将堆顶元素获取走,获取走之后,take()结束之前,会调用是 available.signal() 唤醒下一个条件队列内的等待者,然后释放 lock,下一个等待者被唤醒后去到 AQS 队列,做 acquireQueue(node) 逻辑
72097237 private Thread leader = null;
72107238 ```
72117239
@@ -7219,7 +7247,7 @@ DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先
72197247 if (x == null)
72207248 throw new NullPointerException();
72217249 RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
7222- // 队列锁
7250+ // 队列锁,增加删除数据时都要加锁
72237251 final ReentrantLock lock = this.lock;
72247252 lock.lock();
72257253 try {
@@ -7238,7 +7266,11 @@ DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先
72387266 // 向上调整元素的位置,并更新 heapIndex
72397267 siftUp(i, e);
72407268 }
7241- // 【插入的元素是头节点,原先的 leader 等待的是原先的头节点,所以 leader 已经无效】
7269+ // 情况1:当前任务是第一个加入到 queue 内的任务,所以在当前任务加入到 queue 之前,take() 线程会直接
7270+ // 到 available 队列不设置超时的挂起,并不会去占用 leader 字段,这时需会唤醒一个线程 让它去消费
7271+ // 情况2:当前任务优先级最高,原堆顶任务可能还未到触发时间,leader 线程设置超时的在 available 挂起
7272+ // 原先的 leader 等待的是原先的头节点,所以 leader 已经无效,需要将 leader 线程唤醒,
7273+ // 唤醒之后它会检查堆顶,如果堆顶任务可以被消费,则直接获取走,否则继续成为 leader 等待新堆顶任务
72427274 if (queue[0] == e) {
72437275 // 将 leader 设置为 null
72447276 leader = null;
@@ -7295,11 +7327,13 @@ DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先
72957327
72967328 ```java
72977329 private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
7330+ // 获取尾索引
72987331 int s = --size;
72997332 // 获取尾节点
73007333 RunnableScheduledFuture<?> x = queue[s];
7301- // 置空
7334+ // 将堆结构最后一个节点占用的 slot 设置为 null,因为该节点要尝试升级成堆顶,会根据特性下调
73027335 queue[s] = null;
7336+ // s == 0 说明 当前堆结构只有堆顶一个节点,此时不需要做任何的事情
73037337 if (s != 0)
73047338 // 从索引处 0 开始向下调整
73057339 siftDown(0, x);
@@ -7309,11 +7343,12 @@ DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先
73097343 }
73107344 ```
73117345
7312- * take():阻塞获取头节点,读取当前堆中最小的也就是执行开始时间最近的任务
7346+ * take():阻塞获取头节点,读取当前堆中最小的也就是触发时间最近的任务
73137347
73147348 ```java
73157349 public RunnableScheduledFuture<?> take() throws InterruptedException {
73167350 final ReentrantLock lock = this.lock;
7351+ // 保证线程安全
73177352 lock.lockInterruptibly();
73187353 try {
73197354 for (;;) {
@@ -7323,28 +7358,28 @@ DelayedWorkQueue 是支持延时获取元素的阻塞队列,内部采用优先
73237358 // 等待队列不空,直至有任务通过 offer 入队并唤醒
73247359 available.await();
73257360 else {
7326- // 获取头节点的剩延迟时间是否到时
7361+ // 获取头节点的延迟时间是否到时
73277362 long delay = first.getDelay(NANOSECONDS);
73287363 if (delay <= 0)
7329- // 到时了 ,获取头节点并调整堆,重新选择延迟时间最小的节点放入头部
7364+ // 到达触发时间 ,获取头节点并调整堆,重新选择延迟时间最小的节点放入头部
73307365 return finishPoll(first);
73317366
73327367 // 逻辑到这说明头节点的延迟时间还没到
73337368 first = null;
7334- // 说明有 leader 线程在等待获取头节点,需要阻塞等待
7369+ // 说明有 leader 线程在等待获取头节点,当前线程直接去阻塞等待
73357370 if (leader != null)
73367371 available.await();
73377372 else {
73387373 // 没有 leader 线程,【当前线程作为leader线程,并设置头结点的延迟时间作为阻塞时间】
73397374 Thread thisThread = Thread.currentThread();
73407375 leader = thisThread;
73417376 try {
7377+ // 在条件队列 available 使用带超时的挂起(堆顶任务.time - now() 纳秒值..)
73427378 available.awaitNanos(delay);
7379+ // 到达阻塞时间时,当前线程会从来
73437380 } finally {
7344- // 条件成立的情况:
7345- // 1. 原先 thisThread == leader, 然后堆顶更新了,leader 被置为 null
7346- // 2. 堆顶更新,offer 方法释放锁后,有其它线程通过 take/poll 拿到锁,
7347- // 读到 leader == null,然后将自身更新为leader。
7381+ // t堆顶更新,leader 置为 null,offer 方法释放锁后,
7382+ // 有其它线程通过 take/poll 拿到锁,读到 leader == null,然后将自身更新为leader。
73487383 if (leader == thisThread)
73497384 // leader 置为 null 用以接下来判断是否需要唤醒后继线程
73507385 leader = null;
0 commit comments