Java 内存模型进阶
Java 内存模型(JMM)是并发编程的理论基础,理解 JMM 有助于编写正确的并发代码。
一、happens-before 规则
1.1 规则详解
// 1. 程序次序规则
// 同一线程内,按代码顺序执行
a = 1; // happens-before
b = 2;
// 2. 锁规则
// 解锁 happens-before 后续加锁
synchronized (lock) {
x = 1;
}
// x = 1 的结果对后续获取锁的线程可见
// 3. volatile 规则
// volatile 写 happens-before 后续 volatile 读
volatile boolean flag = false;
// 线程 A
flag = true; // volatile 写
// 线程 B
if (flag) { // volatile 读
// 能看到线程 A 的所有修改
}
// 4. 传递性规则
// A happens-before B, B happens-before C
// 则 A happens-before C
1.2 实战应用
// 双重检查锁定(DCL)
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
// volatile 保证:
// 1. 禁止指令重排
// 2. 写操作对其他线程立即可见
instance = new Singleton();
}
}
}
return instance;
}
}
二、内存屏障
2.1 屏障类型
LoadLoad 屏障:
Load1; LoadLoad; Load2
保证 Load1 在 Load2 之前完成
StoreStore 屏障:
Store1; StoreStore; Store2
保证 Store1 在 Store2 之前刷新
LoadStore 屏障:
Load1; LoadStore; Store2
保证 Load1 在 Store2 之前完成
StoreLoad 屏障:
Store1; StoreLoad; Load2
保证 Store1 在 Load2 之前刷新(最严格)
2.2 volatile 实现
// HotSpot 实现
// volatile 写操作
public void putVolatile(Object value) {
// 1. 写入值
this.value = value;
// 2. 插入 StoreStore 屏障
UNSAFE.storeFence();
// 3. 插入 StoreLoad 屏障
UNSAFE.fullFence();
}
// volatile 读操作
public Object getVolatile() {
// 1. 读取值
Object value = this.value;
// 2. 插入 LoadLoad 屏障
UNSAFE.loadFence();
return value;
}
三、原子性保证
3.1 64 位变量原子性
// long 和 double 是非原子的(32 位 JVM)
private long count; // 读写可能不是原子操作
// 解决方案
// 1. 使用 volatile
private volatile long count;
// 2. 使用 AtomicLong
private AtomicLong count = new AtomicLong(0);
// 3. 使用锁
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
3.2 复合操作原子性
// ❌ 非原子操作
private int count = 0;
public void increment() {
count++; // 包含读 - 改 - 写三个操作
}
// ✅ 原子操作
// 1. synchronized
public synchronized void increment() {
count++;
}
// 2. Atomic
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
// 3. Lock
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
四、指令重排
4.1 重排示例
// 经典重排问题
class ReorderExample {
int a = 0;
boolean flag = false;
// 线程 A
public void writer() {
a = 1; // 1
flag = true; // 2
}
// 线程 B
public void reader() {
if (flag) { // 3
int i = a; // 4
}
}
}
// 可能重排为:2 → 1 → 3 → 4
// 线程 B 看到 flag=true,但 a=0
// 解决方案:使用 volatile
volatile boolean flag = false;
// 禁止 1 和 2 重排
4.2 防止重排
// 使用 volatile 防止重排
private volatile int x = 0;
private volatile int y = 0;
public void write() {
x = 1; // 不会被重排到后面
y = 2; // 不会被重排到前面
}
// 使用 Lock 防止重排
private final Lock lock = new ReentrantLock();
public void write() {
lock.lock();
try {
x = 1; // 锁保证有序性
y = 2;
} finally {
lock.unlock();
}
}
五、总结
JMM 核心要点:
| 规则 | 说明 | 应用场景 |
|---|---|---|
| 程序次序 | 线程内顺序执行 | 单线程场景 |
| 锁规则 | 解锁→加锁可见 | synchronized |
| volatile | 写→读可见 | 状态标志 |
| 传递性 | A→B, B→C 则 A→C | 链式同步 |
理解 JMM 的 happens-before 规则和内存屏障,是编写正确并发代码的基础。