Happens-Before
JMM(Java 内存模型)最核心的概念。
理解 happens-before,是理解 synchronized、volatile、锁等一切并发机制的基础。
为什么需要 Happens-Before
并发环境下,程序执行顺序远没有单线程那么「线性」:
源代码顺序: 实际执行顺序(可能):
a = 1; b = 2;
b = 2; a = 1; ← 重排序
x = a + b; x = a + b;在单线程中,这没问题(as-if-serial 语义保证结果一致)。但在多线程中,如果缺乏适当的同步,这种重排序可能导致可见性问题:
线程A: 线程B:
x = 1; while (y == 0) { }
y = 1; print(x); ← 可能是 0!happens-before 规则定义了:哪些操作的顺序必须被保证,哪些可以被优化。
Happens-Before 的含义
A happens-before B 意味着:
- 可见性:A 对共享变量的修改,B 一定能看到
- 有序性:A 不会重排序到 B 之后
注意:happens-before 不是时间上的先后,而是内存操作的偏序关系。
A happens-before B ≠ A 先于 B 执行
A happens-before B = A 的结果对 B 可见,A 在 B 之前八大规则
| 规则 | 含义 |
|---|---|
| 程序顺序规则 | 单线程中,前面的操作 happens-before 后面的操作 |
| 监视器锁规则 | unlock happens-before 后续的 lock |
| volatile 规则 | volatile 写 happens-before 后续的 volatile 读 |
| 线程启动规则 | start() happens-before 线程内的所有操作 |
| 线程终止规则 | 线程内所有操作 happens-before 其他线程检测到终止 |
| 线程中断规则 | interrupt() happens-before 检测到中断 |
| 终结器规则 | 构造函数 happens-before finalize() |
| 传递性规则 | A HB B,B HB C → A HB C |
程序顺序规则
单线程中,代码写在前面的操作,happens-before 后面的操作:
java
public class ProgramOrderDemo {
public static void main(String[] args) {
int a = 1; // 操作1
int b = 2; // 操作2
int c = a + b; // 操作3
// 操作1 HB 操作2 HB 操作3
// 所以 c 一定是 3
System.out.println(c);
}
}编译器/CPU 可能重排序,但结果不变。
监视器锁规则
java
public class MonitorLockDemo {
private static int value = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lock) {
value = 1; // A: 写操作
} // unlock: 释放锁,刷新缓存
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
// lock: 获取锁,刷新缓存
int x = value; // B: 读操作
System.out.println("读取到: " + x);
}
});
t1.start();
t2.start();
// 锁规则:t1 的 unlock HB t2 的 lock
// 所以 t2 一定能读取到 value=1
}
}volatile 规则
java
public class VolatileHBDemo {
private static int value = 0;
private static volatile boolean ready = false;
public static void main(String[] args) throws InterruptedException {
Thread writer = new Thread(() -> {
value = 42; // A
ready = true; // B (volatile 写)
});
Thread reader = new Thread(() -> {
while (!ready) { // C (volatile 读)
Thread.yield();
}
int x = value; // D
System.out.println("读取到: " + x);
});
// volatile 规则:A HB B,B HB C
// 程序顺序:A HB B,C HB D
// 传递性:A HB C,A HB D
// 所以 x 一定等于 42
writer.start();
reader.start();
}
}这就是 DCL 单例模式中 volatile 的理论基础。
线程启动规则
java
public class ThreadStartDemo {
private static int sharedValue = 0;
public static void main(String[] args) throws InterruptedException {
sharedValue = 42; // 主线程赋值
Thread thread = new Thread(() -> {
// 线程启动规则:start() HB 线程内部所有操作
System.out.println("读取到: " + sharedValue);
});
thread.start(); // start() 发生
thread.join(); // 等待线程结束
}
}子线程一定能看到主线程在 start() 之前的所有操作。
线程终止规则
java
public class ThreadTerminationDemo {
private static int result = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
result = 42;
});
thread.start();
thread.join(); // join() 返回时,子线程的所有操作已完成
// 线程终止规则:子线程操作 HB join() 返回
// 所以一定能读取到 result=42
System.out.println("结果: " + result);
}
}传递性规则
java
public class TransitivityDemo {
private static volatile int a = 0;
private static volatile int b = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
a = 1; // A1
b = 2; // A2 (volatile)
});
Thread t2 = new Thread(() -> {
while (b != 2) { // B1 (volatile)
Thread.yield();
}
int x = a; // B2
System.out.println("x = " + x);
});
// 分析:
// A1 HB A2(程序顺序)
// A2 HB B1(volatile)
// B1 HB B2(程序顺序)
// 传递性:A1 HB B2
// 所以 x 一定等于 1
t1.start();
t2.start();
}
}综合应用:线程安全的单例
java
public class SafeSingleton {
private static volatile SafeSingleton instance;
public static SafeSingleton getInstance() {
if (instance == null) {
synchronized (SafeSingleton.class) {
if (instance == null) {
instance = new SafeSingleton();
// 构造函数中的操作 HB 实例赋值(传递性)
// volatile 规则:赋值 HB 后续的 volatile 读
}
}
}
return instance;
}
private SafeSingleton() {}
}happens-before 分析:
构造函数 → 实例赋值 → volatile 写
↓
volatile 读 → 后续操作(getInstance())规则记忆口诀
单线程内:前 HB 后
锁:unlock HB lock
volatile:写 HB 读
线程通信:start HB 线程内,线程内 HB join
传递性:A HB B,B HB C → A HB C注意事项
- happens-before 不是执行顺序:只是保证可见性和有序性
- 单线程不需要考虑:as-if-serial 语义保证正确性
- 跨线程必须满足:否则可能出现数据竞争
- 不满足 happens-before 的代码:行为未定义,可能出现任何结果
- volatile 的双重作用:防止重排序 + 保证可见性
要点回顾
- happens-before 定义了内存操作的偏序关系
- 满足 happens-before 的操作,结果对其他线程可见
- 八大规则:程序顺序、监视器锁、volatile、线程启动/终止/中断、终结器、传递性
- 传递性是组合规则,将多个 happens-before 串联起来
- 理解 happens-before 是理解 Java 并发机制的基础
