Skip to content

Commit 50c329b

Browse files
committed
Update Java Notes
1 parent 5d48851 commit 50c329b

File tree

2 files changed

+69
-34
lines changed

2 files changed

+69
-34
lines changed

Java.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4414,7 +4414,7 @@ TreeSet 集合自排序的方式:
44144414

44154415
* 直接为**对象的类**实现比较器规则接口 Comparable,重写比较方法:
44164416

4417-
方法:`public int compareTo(Employee o): this 是比较者, o 是被比较者`
4417+
方法:`public int compareTo(Employee o): this 是比较者, o 是被比较者`
44184418

44194419
* 比较者大于被比较者,返回正数(升序)
44204420
* 比较者小于被比较者,返回负数
@@ -5948,8 +5948,8 @@ class Dog{}
59485948
+ 这个集合不能添加,不能删除,不能修改
59495949
+ 但是可以结合集合的带参构造,实现集合的批量添加
59505950

5951-
在Map接口中,还有一个ofEntries方法可以提高代码的阅读性
5952-
+ 首先会把键值对封装成一个Entry对象,再把这个Entry对象添加到集合当中
5951+
在 Map 接口中,还有一个 ofEntries 方法可以提高代码的阅读性
5952+
+ 首先会把键值对封装成一个 Entry 对象,再把这个 Entry 对象添加到集合当中
59535953

59545954
````java
59555955
public class MyVariableParameter4 {

Prog.md

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)