happens-before 是 jmm 中定义操作间可见性与逻辑有序性的语义契约,不约束物理执行顺序;只要 a happens-before b,a 对共享变量的修改对 b 必然可见,且重排序不得破坏该关系。

Happens-before 是 Java 内存模型(JMM)中判断操作可见性与有序性的逻辑标尺,不是时间先后,而是“结果必须被看到”的语义保证。
它不关心物理执行时刻谁先谁后,只承诺:如果 A happens-before B,那么 A 对共享变量的修改,B 一定能看到;且 A 的执行顺序在 JMM 层面逻辑上先于 B。
理解它,关键不在背规则,而在识别“哪些场景能建立这种保证”。
程序顺序规则:同一线程里,代码写在前面的,就对后面的可见
同一段线程代码中,按源码顺序写的操作天然满足 happens-before。
比如:
int a = 1; // 操作A int b = a + 2; // 操作B
A happens-before B,所以 b 一定是 3。
即使 JVM 或 CPU 把 a=1 和 b=a+2 重排(比如先算加法再赋值),只要最终效果等价,就合法——但可见性不能破。
这个规则是单线程安全的基石,也是其他规则的起点。
锁规则:解锁先行于后续加锁
一个线程释放锁(synchronized 块结束),该操作 happens-before 另一个线程随后成功获取同一把锁。
这意味着:
- 线程 A 在 synchronized 块内修改了共享变量;
- 线程 B 进入同一把锁的 synchronized 块后,一定能读到 A 的修改。
Object lock = new Object();
int data = 0;
// 线程A
synchronized (lock) {
data = 42; // 修改
} // ← 解锁发生在此处
// 线程B
synchronized (lock) {
System.out.println(data); // 一定输出 42
}
注意:必须是同一把锁,且 B 的加锁发生在 A 解锁之后(哪怕只是逻辑上,不一定严格时间上)。
volatile 规则:写 volatile 变量先行于后续读该变量
对一个 volatile 变量的写,happens-before 后续任意线程对该变量的读。
更重要的是,它还带“禁止重排序”语义:
- 写 volatile 之前的普通写,不能被重排到 volatile 写之后;
- 读 volatile 之后的普通读,不能被重排到 volatile 读之前。
这使得 volatile 成为轻量级同步手段,常用于状态标志:
volatile boolean ready = false;
int number = 0;
// 线程A
number = 100; // 普通写
ready = true; // volatile 写 → 保证上面那行对B可见
// 线程B
if (ready) { // volatile 读
System.out.println(number); // 一定看到 100
}
没有 volatile,number = 100 可能被重排到 ready = true 后,或 B 读到旧值。
传递性:happens-before 关系可链式推导
这是让规则真正“用起来”的关键。
若 A hb B,且 B hb C,则 A hb C。
典型组合应用:
// 线程A
x = 1; // 普通写
flag = true; // volatile 写
// 线程B
if (flag) { // volatile 读 → flag=true 时,x=1 就一定可见
System.out.println(x);
}
推导链:
-
x = 1hbflag = true(程序顺序) -
flag = truehbif(flag)(volatile 规则)
→x = 1hbSystem.out.println(x)(传递性)
所以 B 能看到 x=1。这就是 volatile 为何能“捎带”发布其他变量的原因。
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











