阶段二:原理与高级机制
约 5868 字大约 20 分钟
2026-03-19
提示
本章节内包含大量重复性内容,因为这些知识点都是相互交织的,在阅读的过程中不免回遇到一些还未解释的概念,不过不用担心,请保持耐心,随着阅读的进度,疑惑会逐步消解。
Java 内存模型(JMM)
一、JMM 是什么?(核心定位)
Java Memory Model(JMM)本质:
一套规范,定义“多线程如何读写共享变量,以及这些操作何时对其他线程可见”。
它解决的是三个根本问题:
- 可见性(Visibility)
- 有序性(Ordering)
- 原子性(Atomicity)
注意: JMM 不是 JVM 内存结构(堆/栈/方法区),而是并发访问规则模型。
二、JMM 抽象结构(必须理解)
JMM 将内存抽象为两层:
主内存(Main Memory)
↑ ↑
线程A工作内存 线程B工作内存规则:
- 所有共享变量 → 存在 主内存
- 每个线程 → 有自己的 工作内存(缓存)
- 线程操作变量:
- 先从主内存拷贝到工作内存
- 在工作内存中操作
- 再写回主内存
问题来源
int x = 0;
// 线程A
x = 1;
// 线程B
System.out.println(x);可能输出:0
原因:
- 线程B读的是自己的工作内存
- 线程A修改还没同步到主内存
三、三大特性(核心)
1、可见性(Visibility)
定义:一个线程修改变量后,其他线程能否立即看到最新值。
public class VisibilityDemo {
private static boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while (flag) {
// 空循环
}
System.out.println("线程结束");
}).start();
// 主线程修改
flag = false;
}
}可能结果:程序永远不会结束
原因
- 子线程一直从工作内存读取 flag
- 主线程修改未同步
正确做法:
private static volatile boolean flag = true;2、原子性(Atomicity)
定义:操作要么全部完成,要么全部不做,不会被打断。
public class AtomicityDemo {
private static int count = 0;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
count++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}结果:不一定是 20000
为什么?原因:count++ 不是原子操作:
1. 读取 count
2. +1
3. 写回多个线程会覆盖写。
synchronized (AtomicityDemo.class) {
count++;
}AtomicInteger count = new AtomicInteger();
count.incrementAndGet();3、有序性(Ordering)
定义:程序执行顺序是否按照代码顺序执行。
现实情况:编译器 / CPU 会进行指令重排序。
int a = 0;
boolean flag = false;
// 线程1
a = 1;
flag = true;
// 线程2
if (flag) {
System.out.println(a);
}可能输出:0
为什么?原因:可能发生重排序。
flag = true;
a = 1;线程 2 看到 flag = true,但 a 还没更新。
四、happens-before(核心规则)
这是 JMM 的“裁判规则”,用于判断:
一个操作是否对另一个操作可见
规则 1:程序顺序规则
A happens-before B规则 2:volatile 规则
volatile 写 → happens-before → volatile 读规则 3:锁规则
unlock → happens-before → lock规则 4:线程启动规则
thread.start() → 子线程所有操作规则 5:线程终止规则
子线程所有操作 → thread.join()规则 5:传递性
A → B,B → C,则 A → C五、代码案例(综合理解)
public class SafePublish {
private int value;
public SafePublish() {
this.value = 42;
}
public int getValue() {
return value;
}
private static volatile SafePublish instance;
public static SafePublish getInstance() {
if (instance == null) {
synchronized (SafePublish.class) {
if (instance == null) {
instance = new SafePublish(); // 安全发布
}
}
}
return instance;
}
}第 13 行的关键代码作用:
- 防止指令重排序
- 保证对象初始化对其他线程可见
否则可能出现:
instance != null,但 value 还没初始化六、特别注意(重点坑位)
volatile 不保证原子性
volatile int count; count++; // ❌ 不安全,count++ 不是原子操作。它只保证可见性!(参见第一阶段内容说明。)
synchronized 同时保证三点
JMM 只是规范,具体实现依赖 CPU
- x86:天然强一致(较少重排序)
- ARM:弱一致(更依赖内存屏障)
JMM ≠ Java 堆内存结构 JVM 内存结构作用是 存储,JMM作用是 并发访问规则
七、Java 21 相关说明
JMM 没有在 Java 21 发生本质变化。虚拟线程(Java 21)依然遵循 JMM。
也就是说:
- 可见性问题仍然存在
- volatile / synchronized 仍然必须用
区别仅在于:
| 维度 | 平台线程 | 虚拟线程 |
|---|---|---|
| 调度 | OS | JVM |
| JMM | ✅ 相同 | ✅ 相同 |
八、总结
- JMM 解决三件事:可见性、原子性、有序性
- 根本原因:线程工作内存 + 主内存、指令重排序
- happens-before 是判断依据
- 三大工具:Volatile、synchronized、CAS
volatile 原理
一、volatile 本质
volatile = 可见性 + 有序性(禁止重排序),不保证原子性
它做了两件事:
- 写操作:立即刷回主内存
- 读操作:强制从主内存读取
底层依赖:内存屏障(Memory Barrier)
二、可见性保障(到底怎么实现)
JMM 只是规范,真正执行在 CPU。
volatile 写
volatile int x;
x = 10;1. 修改工作内存中的 x
2. 立即刷新到主内存
3. 让其他 CPU 缓存失效(缓存一致性协议)volatile 读
int y = x;1. 让当前线程缓存失效
2. 从主内存重新读取 x底层机制(关键)
依赖 CPU 的 缓存一致性协议(MESI):
- 写 volatile → 触发 cache line invalidation
- 其他 CPU → 标记缓存为 invalid
- 下次读取 → 必须从主内存加载
三、内存屏障(核心)
内存屏障是 volatile 的“真正实现”。
屏障类型(必须记住语义,不用死记名字)
| 屏障 | 作用 |
|---|---|
| 写屏障 | 保证前面的写对其他线程可见 |
| 读屏障 | 保证后面的读拿到最新数据 |
volatile 的屏障插入规则(重点)
写 volatile
普通写 → 写屏障 → volatile写效果:
- 保证 volatile 写之前的所有写 不会被重排到后面
- 并强制刷新
读 volatile
volatile读 → 读屏障 → 普通读效果:
- 保证 volatile 读之后的操作 不会被重排到前面
- 并强制读取最新值
四、禁止指令重排序(本质)
volatile 的第二个作用是建立 happens-before 关系
经典问题(DCL 单例为什么需要 volatile)
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}重要
new Singleton() 不是原子操作,实际步骤:
- 分配内存
- 初始化对象
- 将引用赋值给 instance
可能会经历重排序,变成:
- 分配内存
- instance 指向内存
- 初始化对象
其他线程就会看到:instance != null,但对象未初始化完成。
volatile 通过内存屏障禁止 2 和 3 重排序。
五、代码验证(可见性)
public class VolatileTest {
private static boolean flag = true;
public static void main(String[] args) throws Exception {
Thread t = new Thread(() -> {
while (flag) {
// 自旋
}
System.out.println("结束");
});
t.start();
Thread.sleep(1000);
flag = false; // 子线程可能永远看不到
}
}public class VolatileTest {
// 保证可见性
private static volatile boolean flag = true;
public static void main(String[] args) throws Exception {
Thread t = new Thread(() -> {
while (flag) {
// 自旋
}
System.out.println("结束");
});
t.start();
Thread.sleep(1000);
flag = false; // 立即可见
}
}六、volatile vs synchronized
一句话总结:
- volatile:轻量级可见性工具
- synchronized:重量级全功能锁
| 能力 | volatile | synchronized |
|---|---|---|
| 可见性 | ✅ | ✅ |
| 原子性 | ❌ | ✅ |
| 有序性 | ✅ | ✅ |
| 性能 | 高 | 较低(但JDK优化后已不差) |
七、使用场景
适合 volatile
volatile boolean shutdown;volatile Config config;volatile Map cache;不适合 volatile
volatile int count;
count++; // ❌if (flag) {
doSomething(); // ❌ 不是原子操作
}八、本质结论
- volatile 通过内存屏障 + 缓存一致性协议实现可见性
- volatile 通过禁止特定重排序实现有序性
- volatile 不保证原子性
指令重排序
一、本质定义(直接结论)
指令重排序 = 编译器 / CPU 在不改变单线程结果的前提下,改变执行顺序以提升性能。
核心原则:as-if-serial(看起来像串行执行)
只保证:单线程结果正确。不保证:多线程语义正确
二、重排序的三种来源(必须区分)
1.编译器重排序(JIT)
发生时机:Java → 字节码 → 机器码
int a = 1;
int b = 2;可能变为:
b = 2;
a = 1;只要无依赖,就可以交换。
2.CPU 指令级重排序(乱序执行)
CPU 为提高吞吐,会:并行执行无依赖指令、提前执行后续指令。关键机制:指令流水线、乱序执行(Out-of-Order Execution)
3.内存系统重排序(最关键)
写入、读取的“对外可见顺序”发生变化。
来源:Store Buffer(写缓冲)、Load Buffer(读缓冲)、Cache
三、经典问题(必须理解)
int x = 0, y = 0;
int a = 0, b = 0;
// 线程1
a = 1;
x = b;
// 线程2
b = 1;
y = a;可能结果:
x = 0
y = 0 // ❗看起来“不可能”,但真实可能发生原因:
线程1:a=1 → x=b
线程2:b=1 → y=a线程1:x=b(读到0) → a=1(延迟可见)
线程2:y=a(读到0) → b=1(延迟可见)本质上:写操作进入 Store Buffer,读操作绕过写(未同步),导致“看不到彼此的写”。
四、CPU 优化的底层机制
1.Store Buffer(写缓冲)
作用:写操作先进入缓冲区,再异步写入缓存/内存
好处:减少写阻塞、提升吞吐
副作用:其他线程暂时看不到写入
2.Load Buffer(读缓冲)
作用:提前读取数据,提高流水线效率
副作用:可能读取旧值
3. 指令乱序执行
CPU 执行顺序 ≠ 提交顺序
但保证:单线程结果正确(通过 ROB:Reorder Buffer)
五、内存屏障
本质是:一种“禁止某些重排序 + 强制可见性”的指令
| 屏障 | 禁止 |
|---|---|
| StoreStore | 写 → 写 |
| StoreLoad | 写 → 读(最强) |
| LoadLoad | 读 → 读 |
| LoadStore | 读 → 写 |
只需要知道只这个:StoreLoad 屏障最重,成本最高。
六、volatile 如何限制重排序
写 volatile
普通写 → StoreStore → volatile写 → StoreLoad之前的写不能排到 volatile 后面,写对其他线程立即可见。
读 volatile
volatile读 → LoadLoad → LoadStore → 普通读后续操作不能跑到前面,一定读取最新值
七、代码验证
public class ReorderTest {
static int a = 0;
static boolean flag = false;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
a = 1;
flag = true;
});
Thread t2 = new Thread(() -> {
if (flag) {
System.out.println(a); // 可能是 0
}
});
t1.start();
t2.start();
}
}public class ReorderTest {
static int a = 0;
static volatile boolean flag = false;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
a = 1; // 不会被重排到 flag 后
flag = true;
});
Thread t2 = new Thread(() -> {
if (flag) {
System.out.println(a); // 一定是 1
}
});
t1.start();
t2.start();
}
}八、关键结论(压缩)
- 重排序来源三层:编译器 / CPU / 内存系统
- 单线程正确 ≠ 多线程正确(as-if-serial 只保证前者)
- 真正的问题来自:Store Buffer + Cache
- 内存屏障是唯一约束手段
- volatile 本质是:插入屏障
九、工程级注意事项
不要假设执行顺序
a = 1; b = 2;多线程不能假设 a 一定先于 b 被观察到。
“看起来安全”的代码可能不安全
if (flag) {
use(a);
}没有 volatile / 锁 就不安全
synchronized 底层原理
一、核心结论
synchronized 本质是:基于对象头(Mark Word)+ CAS + 操作系统互斥量实现的锁,并带有多级优化(锁升级)机制
它不是单一锁,而是一套自适应锁状态机:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁锁只会升级,不会降级(JDK 8 及以前典型行为)
二、对象头(Object Header)
Java 每个对象在内存中都带“元数据”,锁信息就在这里。
| Mark Word | Klass Pointer | 实例数据 |当前只需要关注 Mark Word。
三、Mark Word(锁的核心载体)
一句话理解:Mark Word 是一块“复用字段”,在不同状态下存不同内容。
| 状态 | 存储内容 |
|---|---|
| 无锁 | hash、GC信息 |
| 偏向锁 | 线程ID |
| 轻量级锁 | 栈中锁记录指针 |
| 重量级锁 | Monitor 指针 |
四、加锁过程(从源码视角理解)
public class SyncDemo {
private final Object lock = new Object();
public void test() {
synchronized (lock) {
// 临界区
}
}
}编译后字节码(关键)
monitorenter
...
monitorexitJVM 执行路径(本质):尝试用 CAS 修改对象头(Mark Word) → 成功:获得锁 失败:进入锁升级流程
五、锁升级机制(核心)
偏向锁(Biased Locking)
概念:假设“锁总是被同一个线程使用”,直接偏向该线程。
工作方式:第一次获取锁:Mark Word 记录线程 ID,之后再进入:不需要 CAS,直接通过。
触发升级
解释:当出现第二个线程竞争→ 撤销偏向锁 → 升级为轻量级锁。
⚠️ Java 21 说明
偏向锁:
- JDK 15 默认关闭
- JDK 18 已移除
所以:在 Java 21 中,偏向锁不存在,锁升级路线:无锁 → 轻量级锁 → 重量级锁
六、轻量级锁(自旋锁)
解释:用 CAS + 自旋 避免线程阻塞。
加锁过程
线程在自己的栈帧创建 Lock Record:Lock Record(锁记录)
CAS 将对象头 → 指向 Lock Record
成功 → 获得锁;失败→ 自旋等待
自旋等待for (;;) { 尝试 CAS }
特点
不阻塞线程(用户态)、适合短时间竞争
问题
如果竞争激烈,自旋浪费 CPU,升级为重量级锁
七、重量级锁(Monitor)
解释:使用操作系统 Mutex,线程进入阻塞。
结构:ObjectMonitor
内部包含:Owner(持锁线程)、EntryList(阻塞队列)、WaitSet(wait集合)
加锁失败流程:线程 → 进入 EntryList → 阻塞(park)
解锁:唤醒一个或多个线程(unpark)
特点:1、有上下文切换(用户态 → 内核态)。2、成本高,但不会浪费 CPU
八、锁升级路径(完整过程)
重要
无锁 ➜ (单线程)偏向锁(Java 21 已移除)➜(轻度竞争)轻量级锁(CAS + 自旋)➜(激烈竞争)重量级锁(阻塞)
九、关键代码(验证锁膨胀)
public class LockUpgradeDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
synchronized (lock) {
// 模拟短临界区
}
}
};
// 多线程竞争
for (int i = 0; i < 10; i++) {
new Thread(task).start();
}
}
}十、synchronized 的真实能力(重新认识)
一句话总结:synchronized = CAS + 自旋 + 阻塞 + 内存屏障
它同时解决:
- 可见性(内存屏障)
- 原子性(互斥)
- 有序性(禁止重排序)
十一、工程级注意点
锁升级不可逆。
锁竞争才是性能问题本质。不是 synchronized 慢,而是:竞争 + 阻塞 才慢
锁对象必须稳定。
synchronized (new Object()) // ❌ 每次都是新锁临界区要尽可能小。锁内代码越少越好
十二、Java 21 关键变化总结
- 偏向锁已移除
- 默认从轻量级锁开始
- 虚拟线程仍然使用 synchronized,但:阻塞会导致 Carrier Thread 被占用(后面模块讲 pinning)
总结
锁信息存储在 Mark Word
加锁本质是 CAS 修改对象头
锁会根据竞争升级:
- 无竞争 → 轻量级锁
- 有竞争 → 自旋
- 激烈竞争 → 阻塞(重量级锁)
synchronized 是一个自适应锁系统。
CAS 与 Unsafe
一、CAS 是什么(从 0 到本质)
定义:CAS(Compare-And-Swap)是一条 CPU 原子指令:如果内存中的值等于期望值,就更新为新值,否则什么都不做。
CAS(V, E, N)
V = 内存中的当前值
E = 期望值(expected)
N = 要写入的新值(new)if (V == E) {
V = N;
return true;
} else {
return false;
}关键点只有一个:这个比较+赋值是原子的(不可被打断),由 CPU 指令(如 x86 的 cmpxchg)保证。
笔记
CAS 是乐观锁(先做再检查)的一种实现方式,synchroized 是悲观锁(先锁再做)。
二、为什么需要 CAS(替代锁)
传统锁会导致线程阻塞、上下文切换成本高。
CAS 思路是:不加锁,直接尝试修改;失败就重试(自旋)
三、CAS 实战模拟(最小实现)
public class CasDemo {
// volatile 保证可见性
private volatile int value = 0;
public void increment() {
int oldValue;
int newValue;
do {
// 1. 读取当前值(可能被其他线程修改)
oldValue = value;
// 2. 计算新值
newValue = oldValue + 1;
// 3. CAS 尝试更新(失败则重试)
} while (!compareAndSwap(oldValue, newValue));
}
/**
* 模拟 CAS(真实由 CPU 指令完成)
*/
private synchronized boolean compareAndSwap(int expected, int newValue) {
if (value == expected) {
value = newValue;
return true;
}
return false;
}
}真实 JVM 中第 3 步不是 synchronized,而是无锁 CPU 指令。
四、Unsafe(CAS 的入口)
提示
CAS 是 CPU 原子指令,Java 本身没有“裸露”的 CAS API,所以,这部分工作交给了 JVM,Unsafe 是 JVM 提供的“后门”,可以直接操作内存和调用 CAS。
public final native boolean compareAndSwapInt(
Object obj, // 目标对象
long offset, // 字段内存偏移量
int expected, // 期望值
int update // 新值
);import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeCasExample {
private static final Unsafe UNSAFE;
private static final long VALUE_OFFSET;
private volatile int value = 0;
static {
try {
// 通过反射获取 Unsafe(受限 API)
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
// 获取 value 字段的内存偏移量
VALUE_OFFSET = UNSAFE.objectFieldOffset(
UnsafeCasExample.class.getDeclaredField("value")
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void increment() {
int oldValue;
do {
oldValue = value;
} while (!UNSAFE.compareAndSwapInt(this, VALUE_OFFSET, oldValue, oldValue + 1));
}
}警告
Java 21 说明
sun.misc.Unsafe仍存在,但不推荐直接使用- 官方替代:
VarHandle
五、原子类(你平时用的其实是 CAS)
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}do {
oldValue = value;
} while (!CAS(value, oldValue, oldValue + 1));结论:AtomicXXX = volatile + CAS + 自旋。
六、ABA 问题(CAS 的致命缺陷)
问题定义:CAS 只比较“值是否相等”,无法判断“值是否被修改过”。
初始值:A
线程1读取 A
线程2:A → B → A(改了两次)
线程1执行 CAS(A → C) → 成功线程1认为“值没变”,但实际上已经被修改过。
说明:CAS 只关心结果,不关心过程。
七、ABA 解决方案(版本号)
核心思路:给变量加一个“版本号”,每次修改版本 +1
import java.util.concurrent.atomic.AtomicStampedReference;
public class AbaSolution {
public static void main(String[] args) {
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(100, 1);
int[] stampHolder = new int[1];
Integer value = ref.get(stampHolder);
int stamp = stampHolder[0];
// CAS:同时比较值 + 版本号
boolean success = ref.compareAndSet(
value, // 旧值
101, // 新值
stamp, // 旧版本
stamp + 1 // 新版本
);
System.out.println(success);
}
}八、CAS 的优缺点
优点很简单:无锁、无阻塞、性能高(低竞争场景)
缺点也很直接:
- 自旋耗 CPU(高竞争时比锁更差)
- ABA 问题
- 只能保证一个变量原子性(多变量要额外设计)
九、体系认知
synchronized:悲观锁(先锁再做)- CAS:乐观锁(先做再检查)
- AQS、原子类、并发容器 → 全部基于 CAS
十、Java 21 注意
不要直接用 Unsafe,使用:
VarHandle
AtomicXXX
LongAdder(高并发计数器)总结
- CAS = CPU 原子指令(比较并交换)
- 实现方式 = 自旋 + volatile
- 原子类本质 = CAS 封装
- 最大问题 = ABA
- 高并发核心体系(AQS/并发容器)全部建立在 CAS 之上
AQS 同步器框架
AQS(AbstractQueuedSynchronizer),AQS = 一个用 CAS 管理状态 + 用队列管理线程的同步框架,几乎所有高级并发工具都建立在它之上。
一、AQS 的最小模型
AQS 内部只有两件核心数据:state(int) + CLH 双向队列(等待线程)
线程先用 CAS 抢 state,失败就进队列排队,被唤醒后再抢。
二、state 是什么(状态管理本质)
state 是一个 volatile int,表示“资源占用情况”,不同工具赋予不同含义:
ReentrantLock:0=未加锁,1=已加锁(可重入则递增)CountDownLatch:剩余计数Semaphore:剩余许可证
核心操作只有两类:
getState()compareAndSetState(expect, update) // CAS
所以,AQS 的本质仍然是:volatile + CAS
三、获取锁的完整流程(独占模式)
tryAcquire() 执行链如下: 成功:结束。失败:入队 → 自旋/阻塞 → 被唤醒 → 再 tryAcquire()
展开
- 线程先直接 CAS 抢
state(一次机会,避免入队开销) - 失败则进入 AQS 队列(一个基于 CLH 的双向链表)
- 在队列中挂起(
LockSupport.park),等待前驱节点释放锁 - 被唤醒后再次尝试 CAS,成功则成为新 owner
四、队列的约束
AQS 使用的是 CLH 变种双向队列,每个节点代表一个线程:
head <-> node1 <-> node2 <-> tail关键约束只有一个:只有当前节点的前驱是 head 时,才有资格去抢锁
这就保证了:有序竞争(接近 FIFO)、避免惊群(不是所有线程同时抢)
五、释放锁(唤醒机制)
释放动作同样简单:tryRelease() → 成功 → 唤醒 head.next
if (释放成功) {
unpark(队列中的下一个线程);
}注意一个关键点:AQS 只唤醒一个后继节点,而不是全部线程
六、AQS 两种模式(理解一次就够)
AQS 只做两种事:
- 独占(Exclusive):同一时刻只能一个线程成功(ReentrantLock)
- 共享(Shared):多个线程可以同时成功(Semaphore / CountDownLatch)
区别只在于:
tryAcquire 返回值语义不同- 独占:成功/失败(boolean)
- 共享:是否还有剩余资源(>=0 表示成功)
七、ReentrantLock 是如何基于 AQS 的
final void lock() {
if (compareAndSetState(0, 1)) {
setOwnerThread(currentThread);
} else {
acquire(1); // 进入 AQS 流程
}
}if (currentThread == owner) {
state++;
}state--;
if (state == 0) {
owner = null;
// 唤醒队列
}八、CountDownLatch 的本质(共享模式)
state 初始化为 N,每次 countDown 就 CAS-1,到 0 时唤醒所有等待线程。
while (state != 0) {
// 进入队列阻塞
}九、Semaphore 的本质(共享资源控制)
state 表示许可证数量,acquire 就 CAS-1,release 就 CAS+1。
关键点:
- 可以多个线程同时成功(共享模式)
- 不需要“只唤醒一个”,可以传播唤醒
十、为什么 AQS 性能好(核心原因)
不是因为它“复杂”,而是因为它避免了两个问题:
- 避免所有线程竞争(通过队列限流)
- 避免频繁阻塞(先 CAS,再决定是否 park)
换句话说:AQS = CAS(快路径) + 队列(慢路径) + park/unpark(阻塞控制)
十一、需要注意的内容
第一,AQS 不保证严格公平,默认是非公平锁(新线程可以插队直接 CAS)。 第二,队列里的线程不是一直自旋,而是会被 park,否则 CPU 会被打爆。 第三,AQS 本身不实现业务语义,只提供框架,具体语义由子类实现(这点极其重要)。
总结
AQS 可以用一句话彻底记住:
笔记
用一个 volatile state 做资源标记;用 CAS 抢占;用队列排队失败线程;用 park/unpark 控制阻塞与唤醒。
再加一句更工程化的理解:
笔记
ReentrantLock、CountDownLatch、Semaphore 的区别只在于“如何解释 state”,底层调度完全一样。
警告
以下内容待更新。
Thread 状态机器
死锁与避免
happens-before 规则
实验验证
虚拟线程调度逻辑简介
Pinning(线程固定) 问题
版权所有
版权归属:FelixJY
