单线程靠as-if-serial语义保证重排序后结果与串行执行一致,多线程需happens-before规则(volatile、锁、程序次序等)加同步机制约束重排序以保障可见性与顺序性。

单线程下结果一致靠 as-if-serial 语义,多线程下靠 happens-before 规则 + 同步机制 来约束重排序,确保可见性与执行顺序。
单线程:as-if-serial 保证逻辑不变
编译器和 CPU 允许重排,但必须满足一个硬性前提:重排后的执行效果,和按源码顺序串行执行的结果完全一样。
- 有数据依赖的指令不会被重排。比如
a = 1; b = a + 2;中,b的计算必须在a赋值之后,否则结果错乱。 - 无依赖的指令可自由调整。比如
x = 10; y = 20;可能先执行y再执行x,但对程序行为无影响。 - 控制依赖(如
if (flag) { ... })在单线程中也可重排,因为分支是否执行不影响最终“看起来像串行”的结果。
多线程:仅靠 as-if-serial 不够,必须引入 happens-before
多个线程共享变量时,一个线程的写可能对另一个线程不可见,或读到未完成初始化的状态。这时需用 happens-before 建立跨线程的先后关系,禁止破坏该关系的重排序。
- volatile 规则:对 volatile 变量的写,happens-before 后续任意线程对该变量的读。它同时禁用写前和读后的重排序。
- 锁规则:解锁操作 happens-before 后续同锁的加锁操作。synchronized 块内所有操作都受此保护,天然串行化。
- 程序次序规则:同一线程内,代码靠前的操作 happens-before 靠后的操作——这是 as-if-serial 在 JMM 中的体现,但仅限单线程内有效。
- 这些规则叠加后具备传递性。例如:A → B(锁释放→锁获取),B → C(volatile 写→volatile 读),就能推出 A → C,形成跨线程安全链。
典型问题与防护手段
常见陷阱如双重检查锁(DCL)单例、标志位初始化等,本质都是因重排序导致其他线程看到“部分构造的对象”或“已置 flag 但未初始化数据”。
- 用
volatile修饰实例引用,可禁止 new 对象过程中的指令重排(如分配内存→初始化→赋值引用),确保引用写入对其他线程可见且有序。 - 用
synchronized包裹读写操作,将临界区变成“单线程执行环境”,彻底规避重排序干扰。 - 避免仅靠普通变量+ sleep / yield 等非同步方式协调线程——它们不提供 happens-before 保证,重排序和缓存可见性问题依然存在。
小结:关键不在“阻止重排”,而在“控制重排边界”
Java 不禁止重排序,而是通过内存模型定义哪些重排是安全的、哪些必须禁止。开发者只需正确使用 volatile、synchronized、Lock、AtomicXXX 等工具,让 JMM 插入必要的内存屏障(Memory Barrier),就能在性能和正确性之间取得平衡。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











