笔记:JUC
一、TODO
二、脑图
Xmind
Edraw
代码洪荒(面试宝典)
👉 https://www.notion.so/JUC-2b2974fbda5e8012975ef0197d6e8237
三、JUC 前置基础
1. 线程的状态
1.1. 操作系统层
操作系统层的线程状态,是操作系统内核对线程的实际调度反映
1.2. Java 应用层
Java 应用层的线程状态,是 JVM 对线程生命周期的抽象和管理,主要反映线程在 Java 内存模型中的行为
NEW- 在
Java应用层,Thread实例已创建,但尚未调用Thread#start,处于未启动状态 - 需要注意的是,我们只是创建了线程实例,但是该实例尚未与操作系统层面的线程建立关联
- 在
RUNNABLE- 可运行状态
- 当我们调用
Thread#start后,JVM会在操作系统层面请求创建一个本地线程 - 一旦操作系统创建了这个本地线程,并将其与
Thread实例 “绑定”,Thread实例就会进入可运行状态 - 可运行状态表示
Thread实例已经准备好运行了,然而,能否真正运行还要取决于操作系统的调度
- 当我们调用
- 运行状态
- 本地线程被分配到
CPU时间片后,开始执行Java代码
- 本地线程被分配到
- 阻塞状态
- 阻塞状态其实就是本地线程进入
IO阻塞 - 需要注意的是:
- 在
Java应用层的IO阻塞、BLOCKED、WAITING、TIMED_WAITING,在操作系统层都属于阻塞状态,CPU不再调用这个线程 IO阻塞严格来说是操作系统层的阻塞,但是我们没必要划分的那么清晰,知道它是什么东西即可
- 在
- 阻塞状态其实就是本地线程进入
- 可运行状态
TERMINATED- 当线程执行完任务后,线程实例就会进入
TERMINATED状态 - 在
Java层面上,线程是一个线程对象,当这个线程对象不再被引用时,会在下一次 GC 时被垃圾回收。
- 当线程执行完任务后,线程实例就会进入
2. 线程相关分类
在 Java 应用层,线程分为:
- 用户线程
- 守护线程
我们平时创建的 Thread 实例默认都是用户线程,main 方法的主线程也是一个用户线程。很多人误以为:只要主线程结束后,JVM 进程就会结束。事实上,这种理解是错误的,只要仍有用户线程在运行,JVM 进程就会继续存活。
但是守护线程的行为则非如此,当所有用户线程都执行完毕后,JVM 进程将自动退出,而不管是否还有守护线程在运行。此时守护线程会被强制终止,其 finally 块中的代码也不保证一定执行。因此守护线程常用于后台服务,例如垃圾回收、心跳监控、日志清理等任务
3. 线程阻塞相关分类
IO阻塞Java程序在与操作系统交互时,由操作系统层代我们执行的阻塞,让我们的本地线程进入阻塞状态- 虽然
IO阻塞在操作系统层面也属于阻塞状态,但对Java应用层来说,它并不能直接感知本地线程是否正处于IO阻塞,因此Thread实例的状态仍为RUNNABLE。简单来说就是:因为感知不到,所以认为还在运行
BLOCKED阻塞、WAITING阻塞、TIMED_WAITING阻塞Java应用层还提供了多种API让本地线程主动进入阻塞状态,Thread实例进入BWTW状态- 因为调用了
Java应用层提供的API,所以Java应用层能知道线程进入了阻塞状态,并且还知道线程进入阻塞状态的 “原因”,所以Thread实例能进入对应的BLOCKED、WAITING或TIMED_WAITING状态 Java应用层提供的相关API主要包括:Thread.sleep- 本地线程进入阻塞状态,
Thread实例进入TIMED_WAITING状态,等待被中断(Thread#interrupt)、阻塞超时
- 本地线程进入阻塞状态,
- 基于
Monitor的阻塞- 竞争
Monitor锁失败synchronized- 本地线程进入阻塞状态,
Thread实例进入BLOCKED状态,并被投递到Monitor的EntryList队列(竞争队列),等待被唤醒(Monitor锁被释放时,由JVM唤醒)
- 本地线程进入阻塞状态,
Object#waitObject#wait()- 本地线程进入阻塞状态,
Thread实例进入WAITING状态,并被投递到Monitor的WaitSet队列(等待队列),等待被唤醒(Object#notify)、被中断
- 本地线程进入阻塞状态,
Object#wait(long timeoutMillis)- 本地线程进入阻塞状态,
Thread实例进入TIMED_WAITING状态,并被投递到Monitor的WaitSet队列(等待队列),等待被唤醒(Object#notify)、被中断、阻塞超时
- 本地线程进入阻塞状态,
Thread#joinThread#join()- 没什么好说的,因为其本质是基于
Object#wait实现的阻塞
- 没什么好说的,因为其本质是基于
Thread#join(final long millis)Thread#join(long millis, int nanos)
- 需要注意的是:
Object#wait和Thread#join都会使Thread实例进入Monitor的等待队列。那么为什么要进入等待队列,而不是进入竞争队列呢?- 进入等待队列是因为
Thread实例在竞争到Monitor锁后,发现某个条件尚未满足,为了不影响其他线程继续获取Monitor锁,所以会主动释放锁,并进入等待队列中等待条件被满足 - 所以
Object#wait和Thread#join的前置条件是:竞争到Monitor锁。只有先竞争到Monitor锁,才知道某个条件尚未满足 - 我的意思是:
Object#wait、Thread#join和synchronized是绑定的,它们必须出现在synchronized代码块中,否则会抛出异常。而Thread.sleep、基于Park的阻塞没有这种限制,可以在任何地方使用 - 需要注意的是:如果竞争到
Monitor锁,但是使用了Thread.sleep、基于Park的阻塞的情况下,Monitor锁是不会被释放的
- 竞争
- 基于
Park的阻塞LockSupport.parkLockSupport.park()- 本地线程进入阻塞状态,
Thread实例进入WAITING状态,等待被唤醒(LockSupport.unpark)、被中断(Thread#interrupt)
- 本地线程进入阻塞状态,
LockSupport.parkNanos(long nanos)- 本地线程进入阻塞状态,
Thread实例进入TIMED_WAITING状态,等待被唤醒(LockSupport.unpark)、被中断(Thread#interrupt)、阻塞超时
- 本地线程进入阻塞状态,
LockSupport.parkUntil(long deadline)- 本地线程进入阻塞状态,
Thread实例进入TIMED_WAITING状态,等待被唤醒(LockSupport.unpark)、被中断(Thread#interrupt)、阻塞超时
- 本地线程进入阻塞状态,
- 需要注意的是,具体的过程比上述描述稍微复杂一点,详见源码:
LockSupport(obsidian内部链接:源码:java.util.concurrent.locks.LockSupport源码解析,Hexo链接: http://blog.wangjia.ink/2025/11/13/源码:java.util.concurrent.locks.LockSupport源码解析/ )
[!NOTE] 注意事项
IO阻塞、Threa.sleep都依赖于操作系统层的队列- 基于
Monitor的阻塞依赖于Java应用层的Monitor中的EntryList、WaitSet队列- 而基于
Park的阻塞,原则上是不依赖于任何数据结构,不过在实际中,几乎所有基于Park阻塞的产品,都会维护一个数据结构(可能是队列,也可能是其他数据结构)。因为如果没有这个数据结构,就无法记录哪些线程被阻塞、以及应当唤醒哪些线程
4. 线程之间的执行关系
1. 串行
串行是指任务按顺序一个接一个地执行,只有当前一个任务执行完成,后一个任务才会开始执行,严格遵循任务提交的先后顺序。即便系统拥有多个 CPU 核心,在串行模式下,任意时刻也只会有一个线程在运行。
2. 并发
并发是指多个线程看起来像是在同时运行,其是通过时间片轮转机制实现,通过快速切换线程,让每个线程都获得运行机会,因此并发特性在单核环境中体现得尤为明显。
而在多核 CPU 上,线程有可能被分配到不同的核心上并行执行,但当线程数量多于核心数量时,CPU 仍需通过时间片轮转进行调度,以确保所有线程都能获得执行机会。
3. 并行
并行是指多个线程在真正意义上同时运行,分别占用不同的 CPU 核心,在同一时刻执行各自的任务,体现出真正的同时处理能力。
5. 线程的活跃性
5.1. 死锁
5.1.1. 死锁概述
死锁是指:两个或两个以上的线程在执行过程中,因互相持有对方所需的资源而造成的一种互相等待的现象。如果没有外力干涉,这些线程都将无法推进下去,永远处于阻塞状态
例如线程 $T_1$ 已经持有了锁 A,现在想去竞争锁 B。线程 $T_2$ 已经持有了锁 B,现在想去竞争锁 A,这便形成了死锁
需要注意的是:在 JUC 中,所谓的 “资源” 是指:锁
5.1.2. 死锁的必要条件
- 互斥条件
- 资源是独占的,同一时刻只能被一个线程持有
- 不剥夺条件
- 线程已持有的资源,在末使用完之前,不能被其他线程强行剥夺,只能由线程自己主动释放
- 循环等待条件
- 若干线程之间形成一种头尾相接的循环等待资源的关系(例如
A等B,B等A)
- 若干线程之间形成一种头尾相接的循环等待资源的关系(例如
- 请求与保持条件
- 线程在等待新资源时,对已经持有的资源保持不放(吃着碗里的,看着锅里的)
[!NOTE] 注意事项
- 解决死锁的核心思路,就是破坏上述四个条件的任意一个。由于 “互斥条件” 是锁的基本特性,通常我们无法破坏,因此主要针对后三个条件进行破坏
5.1.3. 死锁的解决方案
5.1.3.1. 破坏不剥夺条件
破坏不剥夺条件是指:线程等待资源超过一段时间,就放弃等待,并释放已持有的资源
5.1.3.2. 破坏循环等待条件(最常用)
破坏循环等待条件是指:规定所有线程必须按照相同的顺序获取资源。例如规定所有线程必须先获取锁 A,再获取锁 B,再获取锁 C
5.1.3.3. 破坏请求与保持条件
破坏请求与保持条件是指:采用 “预先申请” 的方式,让所有线程必须一次性申请它所需要的所有资源。如果能申请到所有资源,线程继续向下执行。如果不能申请到所有资源,则释放已持有的资源
[!NOTE] 注意事项
- 破坏请求与保持条件,通常和破坏不剥夺条件结合使用
5.2. 活锁
5.2.1. 活锁概述
活锁是指:两个或两个以上的线程在执行过程种,因为不断互相改变对方的终止条件,导致所有线程都无法继续向下执行。
1 | |
[!NOTE] 注意事项
- 活锁本质上是 ”重试“ 导致的冲突
5.2.2. 活锁的解决方案
5.2.2.1. 加锁
活锁本质上并不是 “锁” 本身,但解决活锁时,却是可以使用 ”加锁“ 来解决
5.2.2.2. 随机避让(最常用)
随即避让是指:当多个 ”请求“ 同时争抢资源失败时,不要立即重试,也不要使用固定的休眠时间。更合理的做法是采用 ”指数退避 + 随机抖动“ 的策略,降低竞争冲突
这里同样能使用随即避让能避免这个问题,不过场景不再是 “争抢资源”,而是 “重试” 导致的冲突。我们可以在重试时加入随机避让,减少重试之间的同步碰撞
5.2.2.3. 限制重试次数
5.3. 饥饿
5.3.1. 饥饿概述
饥饿是指:某个线程虽然长期处于可运行状态,但是却一直无法获取所需要的资源(例如 CPU 时间片、锁、资源池资源等),导致线程无法继续执行
5.3.2. 饥饿的解决方案
无论是线程饥饿、流量控制,还是 IO 阻塞,这些问题的本质都是相同的:因无法获取所需要的资源,导致无法继续执行。
针对这种 ”资源匮乏“ 的问题,通常有五种解决方案:
- 强制排队
- 资源预留
- 及时止损
- 异步处理
- 降级处理
- 启动告警
- 异步编程回调
- 随机避让
- 调整优先级
[!NOTE] 注意事项
- 流量控制主要包括并发控制和速率限制
5.3.2.1. 强制排队
强制排队是指:放弃 “自由竞争”,严格遵循 “先来后到” 原则,保证每个 ”请求“ 最终都能得到处理
以锁资源为例,我们可以使用公平锁,而不是非公平锁
5.3.2.2. 资源预留
资源预留是指:提前划分好资源配额,实行资源隔离,做到 ”专款专用“,防止某单一 ”请求“ 耗尽所有资源
以线程资源为例,我们可以为每个业务分配一个独立的线程池,而不是共用一个大的线程池
5.3.2.3. 及时止损
及时止损是指:当通过限时阻塞(超时未获取)或非阻塞(尝试一次未获取)确定拿不到资源的时候,不再继续等待,而是采取以下策略:
- 异步处理
- 利用 ”削峰填谷“ 的思想,将请求写入
MQ
- 利用 ”削峰填谷“ 的思想,将请求写入
- 降级处理
- 启动告警
- 异步编程回调
[!NOTE] 注意事项
- 可能是限时阻塞,也可能是非阻塞,还可能是直接使用异步编程回调(连非阻塞也不用,直接使用异步编程回调)
5.3.2.4. 随机避让
随即避让是指:当多个 ”请求“ 同时争抢资源失败时,不要立即重试,也不要使用固定的休眠时间。更合理的做法是采用 ”指数退避 + 随机抖动“ 的策略,降低竞争冲突
5.3.2.5. 调整优先级
调整优先级是指:调整竞争权重,让核心的 ”请求“ 拥有更高的优先级
以 CPU 时间片资源为例,我们动态调整线程的优先级
[!NOTE] 注意事项
- 虽然
Java提供了Thread#setPriority,但这通常不是一个靠谱的解决方案,更多时候我们要反其道而行之:尽量不要修改线程的优先级,让所有线程的优先级保持同等水平
8. 线程相关常用方法
线程相关的常用方法主要包括:
Thread中的一些的方法TimeUnit中的一些方法TimeUnit#sleepTimeUnit#timedJoinTimeUnit#timedWait
Object中的一些方法Object#waitObject#notify
LockSupport中的一些方法
[!NOTE] 注意事项
- 详见源码:
Thread
obsidian内部链接:Hexo链接:- 详见源码:
Object
obsidian内部链接:Hexo链接:- 详见源码:
LockSupport
四、ThreadLocal
ThreadLocal 是指:Thread->threadLocals,用于记录该 Thread 实例独属的一些信息。而 Thread->threadLocals 又是 ThreadLocal.ThreadLocalMap 类型,详见源码:ThreadLocal<T>.ThreadLocalMap(obsidian 内部连接:源码:java.lang.ThreadLocal<T>.ThreadLocalMap源码解析,Hexo 链接: http://blog.wangjia.ink/2025/11/16/源码:java.lang.ThreadLocal<T>.ThreadLocalMap源码解析/ )
五、锁
1. 锁基础体系

- 详见源码:
Serializableobsidian内部链接:Hexo链接:
- 详见源码:
Lock - 详见源码:
ReadWriteLock - 详见源码:
Condition - 详见源码:
AbstractOwnableSynchronizer - 详见源码:
AbstractQueuedSynchronizer - 详见源码:
AbstractQueuedLongSynchronizer
2. synchronized
2.1. synchronized 前置基础
2.1.1. Java 对象头
通常我们的一个 Java 实例,他在堆内存中由三部分组成:
Java对象头(Object Header)- 实例数据(
Instance Data) - 对齐填充(
Padding)
以 32 位虚拟机为例:
[!NOTE] 注意事项
- 这里的
Klass Word应该叫做Klass Pointer
2.2. synchronized 概述
synchronized 是一种可重入、悲观、非公平、互斥的锁
2.3. synchronized 使用方式
以如下代码为例,如果 Room#increment 竞争到了锁,那么 Room#decrement、Room#getCounter 都无法再竞争到锁
1 | |
[!NOTE] 注意事项
- 当我们为静态具体方法加上
synchronized修饰符,锁的是类对象(即Class对象)
1 | |
[!NOTE] 注意事项
2. 当我们为实例普通方法加上synchronized修饰符,锁的是仅是当前对象(即this)
1 | |
[!NOTE] 注意事项
3. 普通实例方法并不必然只能锁当前对象 (this),静态方法也并非只能锁类对象(Class对象)
1 | |
[!NOTE] 注意事项
4. 以如下代码为例,虽然Room#sleep和Room#study本身没有逻辑上的交集,我们可以通过引入多把锁来进行优化:
1 | |
[!NOTE] 注意事项
5. 即便一个方法中,只涉及到了共享变量的读操作,我们仍然要为其加锁。这主要是为了其可见性、原子性。可见性可以通过多种方式来保证,因此并不一定需要加锁。但原子性必须依赖锁来确保,避免B线程在进行读操作时,A线程进行写操作,从根本上杜绝脏读、不可重复读、幻读等问题
6. 对于synchronized的本质,我们可以简单的理解为:通过为某对象或某类对象的加锁,从而控制多线程对共享资源的读写操作
2.4. synchrnoized 锁升级
锁升级的流程为:偏向锁 ➔ 轻量级锁 ➔ 重量级锁
2.4.1. 偏向锁
偏向锁的设计初衷,是在无竞争场景下进一步降低竞争锁的开销,从而提升锁操作的性能
当某线程(例如 $T_1$) 要去执行临界区代码,JVM 会通过 CAS 尝试将 $T_1$ 的 Thread ID 写入被加锁对象或被加锁类对象的对象头中的 Mark Word 中。
如果写入成功, $T_1$ 顺利执行临界区代码。如果 $T_1$ 后续再次执行临界区代码,只需检查 Mark Word 中的 Thread ID 是否与自身一致(由于偏向锁在释放时并不会主动重置对象头,因此在无竞争场景下,Mark Word 中依旧保留着 $T_1$ 的 Thread ID,所以能直接执行)
如果写入失败,则是其他线程(例如 $T_2$)正在持有偏向锁(无论 $T_2$ 是否在执行临界区代码),而这就发生了竞争,偏向锁模式就会宣告失败。此时 JVM 会将偏向锁升级为轻量级锁
我们可以简单的理解为:一旦某个线程持有了偏向锁,它就 “偏向” 这个线程,从头到尾都只认它,无论该线程是否在执行临界区代码。但是一旦出现第二个线程尝试竞争锁,偏向锁就会被撤销并升级为轻量级锁
然而,随着硬件性能提升和虚拟机其他优化手段的发展,偏向锁带来的性能收益已逐渐减弱,同时其实现的复杂性也成为阻碍 JVM 进一步优化的负担。除此之外,在高并发场景中,偏向锁撤销的开销往往大于其带来的收益。因此,Oracle 在 Java15 之后将偏向锁标记为废弃,并在 Java17 中将其彻底移除。
[!NOTE] 注意事项
- 如果使用的是
Java8,偏向锁默认是开启的,我们可以通过JVM启动参数来关闭偏向锁
1 | |
[!NOTE] 注意事项
2. 由于Mark Word的空间是有限的,存储了HashCode就没地方存Thread ID了。因此,如果在无锁状态计算了HashCode,就无法再使用偏向锁,而是直接使用轻量级锁。如果在持有偏向锁时计算了HashCode,偏向锁会被立即撤销,并直接升级为重量级锁(因为重量级锁的Monitor中可以存储HashCode)
2.4.2. 轻量级锁
轻量级锁的设计初衷,是在轻量竞争场景下进一步降低竞争锁的开销,从而提升锁操作的性能
当某线程(例如 $T_1$)要去执行临界区代码,JVM 会在 $T_1$ 的虚拟机栈的栈帧中创建一个锁记录(Lock Record)
然后 JVM 将 Lock Record 的 Object reference 指向被加锁对象或被加锁类对象,并且通过 CAS 尝试将 Lock Record 的 lock record 地址 00 与被加锁对象或被加锁类对象的 MarkWord(即 Hashcode Age Bias 01)进行交换
如果交换成功, $T_1$ 顺利执行临界区代码。如果在 $T_1$ 执行临界区代码时发生了重入,同样会添加新的 Lock Record。$T_1$ 的虚拟机栈的栈帧中有多少个 Lock Record,说明发生了多少次重入
当 $T_1$ 执行完临界区的代码,JVM 会将两者再交换回来
如果交换失败,则是其他线程(例如 $T_2$)持有轻量级锁,那么 $T_1$ 会进行多次自旋。如果多次自旋仍热未成功,JVM 会将轻量级锁升级为重量级锁
2.4.3. 重量级锁
监视器(Monitor、管程)是 JVM 内部专门用于实现重量级锁的结构。当升级为重量级锁时,JVM 会为被加锁对象或被加锁类对象创建一个 Monitor 对象
当 $T_1$ 执行完临界区代码后,会根据线程中保存的指向被加锁对象或被加锁类对象的地址,找到该对象或类对象,然后再根据该对象的对象头的 Mark Word 找到 Monitor,将其 Owner 设置为 null,并唤醒 Monitor 中 EntryList 中所有等待的线程,这些线程随后开始竞争重量级锁( Monitor 的 Owner)
3. ReentrantLock
详见源码:ReentrantLock
obsidian内部链接:Hexo链接:
4. ReentrantReadWriteLock
详见源码:ReentrantReadWriteLock
obsidian内部链接:Hexo链接:
5. CAS
5.1. CAS 概述
CAS 是一种思想,其核心思想是:当前线程在进行写操作时,当 V = E 时,修改为 N。当 V ≠ E 时,不进行任何操作。其中 V、E、N 是指:
V(Current Value)- 当前线程在进行写操作时,共享变量在主内存中的实际值
E(Expected Value)- 当前线程在进行写操作时,期望共享变量在主内存中的实际值
- 简单来说就是:$t_1$ 时从主内存拿到的共享变量,我们期望 $t_2$ 时主内存中的共享变量仍然是这个值
N(New Value)- 当前线程在进行写操作时,要把共享变量修改为该值
为了实现 CAS 思想,不同的 CPU 厂商提供了不同的原子性的机器码指令(例如 x86 是 CMPXCHG,ARM 是 LDXR/STXR 组合),我们一般把这些机器码指令称之为 “CAS 指令”
在日常交流中,我们常把把 CAS 和 CAS 指令混为一谈。在 Java 中,说到 “使用 CAS”,其实就是通过 Unsafe 调用了 CAS 指令
[!NOTE] 注意事项
CAS本质上并不是一种锁,而是一种更新机制- 虽然
V说是 ”当前线程在进行写操作时,共享变量在主内存中的实际值“,但是由于JMM的存在,我们可能仍从工作内存读V,所以有可见性问题。除此之外,还有有序性问题。因此即便使用CAS,通常结合volatile修饰符使用,CAS保证原子性,volatile保证可见性、顺序性- 当我们使用
CAS时,目标肯定是希望写操作成功。所以我们通常会循环执行CAS,直到写操作成功
5.2. CAS 问题爆破
5.2.1. ABA 问题
5.2.1.1. ABA 问题概述
ABA 问题是指:当 V = E 时,尽管 CAS 操作成功了,但实际上该位置的数据可能曾经发生过变化。例如我们桌子上有一杯水(A),你离开了一会儿。期间别人把你杯子里的水喝了(变成 B),然后又重新倒满水(变回 A)。等你回来时,看到水还是满的,你以为没人动过,但实际上里面的水已经变了
很多时候 ABA 问题可能无伤大雅。但是在一些业务逻辑时,这个问题可能是致命的
5.2.1.2. ABA 问题解决方案
5.2.1.2.1. 版本号
版本号是指:不仅比较值(即 V = E),还要比较版本号。即 $CAS(E, N)➔CAS(E + Version_E, B + Version_B)$,当线程在进行写操作时,不仅要 V = E,还要 $Version_V=Version_E$
而 AtomicStampedReference 就采用了这种方式解决了 ABA 问题。
除此之外,AtomicMarkableReference 可以看作是 AtomicStampedReference 的简化版,但是它并没有严格使用版本号。AtomicMarkableReference 是为了解决 “很多时候我们并不需要知道它被修改了多少次,而是只需要知道它是否被修改过” 的问题
5.3. Atomic* 原子类
5.3.1. Atomic* 原子类概述
Atomic* 原子类是使用 CAS 实现多线程并发安全的一些类
5.3.2. Atomic* 原子类相关分类
AtomicBooleanAtomicIntegerAtomicIntegerArrayAtomicIntegerFieldUpdaterAtomicIntegerFieldUpdaterImplAtomicLongAtomicLongArrayAtomicLongFieldUpdaterAtomicReferenceAtomicReferenceArrayAtomicReferenceFieldUpdaterAtomicReferenceFieldUpdaterImplAtomicMarkableReferenceAtomicStampedReferenceLongAdderDoubleAdder
六、并发工具
1. CountDownLatch
详见源码:CountDownLatch
obsidian内部链接:Hexo链接:
2. CyclicBarrier
详见源码:CyclicBarrier
obsidian内部链接:Hexo链接:
3. Semaphore
详见源码:Semaphore
obsidian内部链接:Hexo链接:
七、线程池
1. 线程池基础体系

- 详见源码:
Executor - 详见源码:
ExecutorService - 详见源码:
AbstractExecutorService - 详见源码:
ScheduledExecutorService - 详见源码:
ThreadPoolExecutor - 详见源码:
ScheduledThreadPoolExecutor - 详见源码:
ForkJoinPool
[!NOTE] 注意事项
- 线程池的三架马车:
ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool
八、异步编程
1. 异步编程基础体系

- 详见源码:
Serializableobsidian内部链接:Hexo链接:
- 详见源码:
Comparable<T>obsidian内部链接:Hexo链接:
- 详见源码:
Callable<V> - 详见源码:
Runnable - 详见源码:
Delayed - 详见源码:
Future<V> - 详见源码:
RunnableFuture<V> - 详见源码:
ScheduledFuture<V> - 详见源码:
ForkJoinTask<V> - 详见源码:
FutureTask<V> - 详见源码:
RunnableScheduledFuture<V> - 详见源码:
ScheduledFutureTask<V> - 详见源码:
RecursiveAction - 详见源码:
RecursiveTask<V> - 详见源码:
CountedCompleter<T>
2. 异步编程实现方式
2.1. 伪异步
伪异步是指:当前线程将我们执行的 Java 代码中,一些耗时的操作交给其他线程(通常使用专门的线程池)去执行,当前线程可以直接继续向下执行
2.2. 伪异步 + 异步编程等待
伪异步 + 异步编程等待是指:虽然当前线程将我们执行的 Java 代码中,一些耗时的操作交给其他线程(通常使用专门的线程池)去执行,但当前线程仍然需要该任务的返回结果,所以当前线程必须在后续某个时刻获取该任务的返回结果
通常的做法是:提交任务后返回一个凭证(即 Future,常见具体实现类主要包括 FutureTask、ScheduledFutureTask、ForkJoinTask),然后根据这个凭证在需要结果时获取结果,然后再继续向下执行
[!NOTE] 注意事项
- 异步编程等待既可能是阻塞的也可能是限时阻塞的
2.3. 伪异步 + 异步编程回调
这部分内容涉及到了 CompletableFuture,详见笔记:Java 函数式编程(obsidian 内部链接:笔记:Java函数式编程,Hexo 链接:)
