happens-before 是用于推理多线程安全性的语义规则,定义操作间内存可见性与逻辑顺序,不禁止重排序但要求结果等价;它非物理时间承诺,而是通过内存屏障约束底层重排以保障一致性。

happens-before 不是执行顺序的硬性规定,而是内存可见性与操作顺序的逻辑约束。它不禁止重排序,但要求重排序后的结果必须满足该关系所定义的可见性保证。
happens-before 是什么,不是什么
它不是一个“时间先后”的物理承诺,而是一套用于推理多线程安全性的语义规则。只要两个操作之间存在 happens-before 关系,那么前一个操作的结果(比如变量写入)对后一个操作(比如变量读取)就是可见的;但 JVM 和 CPU 仍可重排指令——只要最终效果等价于按 happens-before 顺序执行。
例如:
- 单线程内,
x = 1;happens-beforey = 2;,编译器可能把它们调换顺序,但若y不依赖x,结果不变,就允许重排; - 跨线程时,若线程 A 写
volatile flag = true;,线程 B 读到flag == true,则 A 中 flag 写之前的所有操作(包括非 volatile 变量的写),对 B 来说都可见——这是 volatile 规则带来的 happens-before 传递效应,而非靠“不准重排”来实现。
指令重排的三层来源与约束边界
重排发生在三个层面:编译器优化、CPU 指令级并行、缓存/写缓冲区行为。它们共同目标是提升性能,但都受 as-if-serial 语义限制:单线程下,重排不能改变程序结果。
立即学习“Java免费学习笔记(深入)”;
关键点在于:
- 数据依赖的操作不会被重排(如
a = 1; b = a + 1;中,b 的计算必须在 a 赋值之后); - 无依赖操作(如
a = 1; b = 2;)很可能被重排,这在多线程中就可能引发问题; - happens-before 规则正是为这类无依赖操作划定“不可跨越”的逻辑边界,比如解锁与后续加锁之间、volatile 写与后续读之间。
八条 happens-before 规则如何实际起作用
这些规则不是凭空设计的,每一条都对应一种同步场景,并隐式插入内存屏障(memory barrier)来限制底层重排:
- 程序顺序规则:同一线程内,代码顺序即 happens-before 顺序,是其他规则的基础;
- 监视器锁规则:解锁动作会刷新本线程写缓存,加锁动作会清空本地读缓存,从而确保临界区外的可见性;
- volatile 规则:写 volatile 变量时插入 StoreStore + StoreLoad 屏障,读时插入 LoadLoad + LoadStore 屏障,阻止相关读写被重排穿过;
- 线程启动/终止规则:start() 和 join() 方法内部已封装屏障,使主线程对共享变量的修改对新线程可见,或子线程的修改对主线程可见;
- 传递性:让多个独立规则能链式推导,比如 A hb B,B hb C ⇒ A hb C,是构建复杂同步逻辑的关键。
典型误用:以为加了 volatile 就万事大吉
volatile 仅保证单个变量的可见性和禁止特定重排,不保证复合操作的原子性。例如:
if (flag) { x++; } —— flag 是 volatile,但 x++ 本身包含读-改-写三步,即便 flag 可见,x 的读和写仍可能被其他线程穿插,导致丢失更新。
这时候需要 synchronized 或 AtomicInteger 等真正提供原子性的机制,而 happens-before 规则只是告诉你:在哪些点上你能确信看到什么,而不是帮你自动完成同步。


















