happens-before 是语义契约而非执行禁令,它不禁止重排,而是规定A happens-before B时A的结果对B可见且动作已完成;JVM和CPU可在不破坏该契约前提下自由重排。

Java 的 happens-before 原则不是用来“防止”指令重排的,而是为重排划出安全边界——只要满足 happens-before 关系,JVM 就保证结果等价于按该顺序执行,即使实际指令被重排了,你也不用担心逻辑出错。
happens-before 是语义契约,不是执行禁令
它不禁止编译器或 CPU 重排,而是规定:如果 A happens-before B,那么 A 的结果(如变量写入)对 B 一定可见,且 A 的所有动作在 B 看来都已完成。JVM 和 CPU 可以在不破坏这个契约的前提下自由重排。
- 比如 volatile 写之后的读,虽然可能重排,但读一定能看到写的结果
- 比如 synchronized 块退出 happens-before 下一个块进入,锁释放和获取之间的操作不会“乱序穿透”
- 重排仍会发生,但被 happens-before 规则“兜底”,程序员只需关注规则,不用盯底层指令
常见 happens-before 规则如何约束重排
每条规则都隐含对重排的限制范围:
- 程序次序规则:单线程内,代码顺序中靠前的操作 happens-before 靠后的操作 → 编译器不能把后边的读重排到前边的写之前(若影响可见性)
- volatile 规则:volatile 写 happens-before 后续任意线程对该变量的读 → 写前的所有操作不能被重排到写之后,读后的所有操作不能被重排到读之前
- 锁规则:unlock happens-before 后续 lock → 解锁前的写对加锁后的读可见,相关重排被禁止跨锁边界
- 线程启动规则:Thread.start() 之前的操作 happens-before 子线程的任意动作 → 主线程初始化数据可安全被子线程看到
重排真实存在,但 happens-before 让它“不可见”
你可以用 JOL 或 JITWatch 观察到字段读写被重排,甚至用 Unsafe 手动触发;但一旦引入 happens-before 边界(如 volatile、synchronized),JVM 会插入内存屏障(Memory Barrier),告诉 CPU:“这里前后不能乱穿”。
立即学习“Java免费学习笔记(深入)”;
- 普通变量赋值可能被重排,volatile 写则强制刷新到主存并禁止其前后指令越过它
- CPU 的 StoreLoad 屏障(最重)常出现在 unlock/volatile 写之后,阻断重排
- 没 happens-before 关系的操作,重排可能导致 DCL(双重检查锁定)失效、状态错乱等问题
写代码时怎么配?关键在建立明确的 happens-before 链
不要试图靠“代码上下顺序”保可见性,要主动构造链:
- 共享变量用 volatile,让写-读形成 happens-before
- 状态变更+通知用 synchronized,利用锁的进出规则
- 生产者初始化数据后调用 countDown(),消费者 await() 后读 —— CountDownLatch 建立跨线程 happens-before
- 避免“伪共享”或仅靠 final 字段初始化(final 有特殊 happens-before,但仅限构造结束瞬间)
不复杂但容易忽略:happens-before 是你和 JVM 的约定,重排是它的自由,而你负责把关键路径用规则连起来。


















