Java 内存模型 (JMM) 详解
Java 内存模型是并发编程的理论基础,理解 JMM 对编写正确的多线程程序至关重要。
一、为什么需要 JMM
1.1 硬件内存架构
CPU → 缓存 (L1/L2/L3) → 主内存
多核 CPU:
CPU Core 1 → L1/L2 缓存 ──┐
CPU Core 2 → L1/L2 缓存 ──┼→ 主内存
CPU Core 3 → L1/L2 缓存 ──┘
问题:多核处理器各自有缓存,导致缓存不一致。
1.2 并发问题根源
// 共享变量
int count = 0;
// 线程 A
count++; // 1. 读取主内存到工作内存
// 2. count + 1
// 3. 写回主内存
// 线程 B 同时执行 count++
// 结果可能丢失更新!
1.3 JMM 的作用
JMM 屏蔽底层硬件差异
↓
定义线程如何与工作内存、主内存交互
↓
保证原子性、可见性、有序性
二、JMM 核心概念
2.1 主内存与工作内存
graph TB
subgraph 主内存
M[共享变量]
end
subgraph 线程 A
A1[A 的工作内存]
A2[A 的变量副本]
end
subgraph 线程 B
B1[B 的工作内存]
B2[B 的变量副本]
end
M -->|读取 | A1
A1 -->|写入 | M
M -->|读取 | B1
B1 -->|写入 | M
规则:
- 所有变量存储在主内存
- 每个线程有独立的工作内存
- 线程不能直接访问其他线程的工作内存
2.2 内存间交互操作
| 操作 | 说明 |
|---|---|
| lock | 锁定主内存变量 |
| unlock | 解锁主内存变量 |
| read | 从主内存读取到工作内存 |
| load | 将 read 的值放入工作内存副本 |
| use | 将工作内存值传递给执行引擎 |
| assign | 将执行引擎结果赋值给工作内存 |
| store | 将工作内存值传送到主内存 |
| write | 将 store 的值写入主内存变量 |
三、三大特性
3.1 原子性 (Atomicity)
定义:一个或多个操作要么全部执行成功,要么全部失败。
// ❌ 不保证原子性
int count = 0;
count++; // 非原子操作(读 - 改 - 写)
// ✅ 保证原子性
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作
JMM 保证:
read、load、assign、use等操作是原子的long、double的 64 位操作可能不保证原子性(32 位 JVM)
3.2 可见性 (Visibility)
定义:一个线程修改共享变量后,其他线程能立即看到。
// ❌ 不保证可见性
boolean flag = false;
// 线程 A
flag = true; // 修改后可能还在工作内存
// 线程 B
while (!flag) { // 可能永远看不到修改
// ...
}
// ✅ 保证可见性
volatile boolean flag = false;
// volatile 强制刷新到主内存
JMM 保证:
volatile保证可见性synchronized、Lock保证可见性
3.3 有序性 (Ordering)
定义:程序执行顺序按照代码的先后顺序。
// 可能发生指令重排
int a = 1; // 1
int b = 2; // 2
int c = a + b; // 3
// 优化后可能变成:2 → 1 → 3
JMM 保证:
- 线程内表现为串行
- 线程间通过 happens-before 保证有序性
四、happens-before 规则
4.1 规则定义
happens-before:如果一个操作 happens-before 另一个操作,那么前者的结果对后者可见。
4.2 八大规则
1. 程序次序规则
线程内每个操作 precedes 后续操作
2. 锁规则
unlock 操作 happens-before 后续 lock 操作
3. volatile 规则
volatile 写操作 happens-before 后续 volatile 读
4. 传递性规则
A happens-before B, B happens-before C → A happens-before C
5. 线程启动规则
Thread.start() happens-before 线程内任何操作
6. 线程终止规则
线程内任何操作 happens-before Thread.join()
7. 线程中断规则
interrupt() happens-before 被中断线程检测到
8. 对象终结规则
对象构造完成 happens-before finalize()
4.3 实战示例
// 示例 1:volatile 保证有序性
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // volatile 防止重排
}
}
}
return instance;
}
}
// 示例 2:synchronized 保证可见性
class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 释放锁前刷新到主内存
}
public synchronized int getCount() {
return count; // 获取锁后从主内存读取
}
}
五、内存屏障
5.1 什么是内存屏障
内存屏障 = CPU 指令 + 编译器屏障
作用:
1. 禁止屏障两侧的指令重排序
2. 强制刷新缓存到主内存
5.2 四种屏障类型
| 屏障类型 | 作用 |
|---|---|
| LoadLoad | 禁止读 - 读重排序 |
| StoreStore | 禁止写 - 写重排序 |
| LoadStore | 禁止读 - 写重排序 |
| StoreLoad | 禁止写 - 读重排序(最严格) |
5.3 volatile 实现原理
// volatile 写操作
volatile int a = 1;
// 插入屏障
storeStore(); // 禁止上面的写与下面的写重排
storeLoad(); // 禁止上面的写与下面的读重排
// volatile 读操作
int b = a;
// 插入屏障
loadLoad(); // 禁止下面的读与上面的读重排
loadStore(); // 禁止下面的读与上面的写重排
六、指令重排
6.1 重排原因
编译器优化 → 指令重排
CPU 乱序执行 → 指令重排
内存系统优化 → 指令重排
6.2 重排示例
// 经典重排案例
int x = 0; // 1
boolean flag = false; // 2
// 线程 A
x = 1; // 3
flag = true; // 4
// 线程 B
while (flag) { // 5
System.out.println(x); // 6
}
// 可能重排为:4 → 3 → 5 → 6
// 线程 B 看到 flag=true,但 x 还是 0!
6.3 禁止重排
// 使用 volatile 禁止重排
volatile boolean flag = false;
// 线程 A
x = 1;
flag = true; // volatile 写,禁止与上面重排
// 线程 B
while (flag) { // volatile 读
System.out.println(x); // 保证看到 x=1
}
七、final 字段语义
7.1 final 保证可见性
class FinalExample {
final int x;
int y;
public FinalExample() {
x = 3;
y = 4;
}
}
// 构造对象
FinalExample obj = new FinalExample();
// 其他线程读取
int a = obj.x; // ✅ 保证看到 3(final)
int b = obj.y; // ❌ 可能看到 0(非 final)
7.2 写 final 域的重排序规则
构造函数内:
1. 在 final 域写之后,不能将构造函数的引用赋值给外部变量
2. 保证 final 域初始化完成后才能被其他线程看到
八、总结
JMM 核心要点:
| 特性 | 保证机制 | 实现方式 |
|---|---|---|
| 原子性 | synchronized、Lock、Atomic | 锁、CAS |
| 可见性 | volatile、synchronized | 内存屏障、锁 |
| 有序性 | volatile、happens-before | 内存屏障 |
| 规则 | 说明 |
|---|---|
| happens-before | 判断数据依赖关系 |
| 内存屏障 | 禁止指令重排 |
| volatile | 轻量级同步机制 |
JMM 是并发编程的基础,理解它有助于编写正确的多线程代码。