重排序在单线程中安全,在多线程中危险:单线程受as-if-serial语义保护,结果不变;多线程因缺乏跨线程依赖约束,易引发可见性、有序性问题,需用volatile、锁或内存屏障控制。

重排序在单线程中不会破坏程序正确性,但在多线程中可能引发可见性、有序性和安全性问题。关键区别在于:单线程受 as-if-serial 语义保护,而多线程依赖 happens-before 关系来约束重排序行为。
单线程下重排序为什么安全?
编译器和 CPU 允许重排序,但必须满足两个硬约束:
- 不改变存在数据依赖的操作顺序(如
a = 1; b = a;中写后读不能颠倒) - 最终执行结果与按源码顺序执行一致(即遵守 as-if-serial 语义)
例如:int a = 10; int b = 20; int c = a + b; int d = a * b;,a 和 b 的赋值可互换,c 和 d 的计算顺序也可调整——只要它们都发生在使用前,结果不变。这种优化对程序员透明,无需干预。
多线程下重排序为何危险?
问题核心在于:不同线程之间没有天然的数据依赖约束,重排序会打破程序员隐含的执行逻辑假设。典型表现有:
立即学习“Java免费学习笔记(深入)”;
-
写操作被重排到标志位之后:如
a = 1; flag = true;被重排为先设flag再赋a,另一线程看到flag == true却读到a == 0 -
读操作被猜测执行提前:如
if (flag) { x = a; }中,CPU 可能预加载a,再判断flag,导致读到未初始化的值 - 无同步时,重排序+缓存不一致共同放大问题:一个线程的写入未及时刷出,另一个线程又因重排序读取了旧缓存值
怎么识别和控制重排序风险?
不是所有重排序都需要干预,重点防范跨线程的“非预期顺序”。实用策略包括:
- 用 volatile 修饰状态标志(如
flag),它禁止写前/读后重排序,并插入内存屏障,保证写立即可见 - 用 synchronized 或 Lock 包裹共享变量读写,利用锁的 happens-before 规则建立顺序约束
- 避免在无同步下依赖“先写 A 后写 B”的执行顺序——若逻辑强依赖,应显式加锁或用 volatile 配合原子操作
- 构造对象时慎用双重检查锁定(DCL),未加 volatile 的 instance 字段可能被重排序导致返回未完全初始化的对象
底层靠什么实现约束?
Java 不直接暴露硬件细节,但效果由两层机制协同保障:
- 内存屏障(Memory Barrier):JVM 在 volatile 读写、synchronized 进出等位置插入屏障指令,禁止特定方向的重排序(如 StoreLoad 屏障防止写后读乱序)
- 缓存一致性协议(如 MESI):确保一个 CPU 核心修改变量后,其他核心能及时失效本地缓存并重新加载最新值
这两者共同支撑了 happens-before 的语义落地:不是强制按代码顺序执行,而是保证“前操作的结果对后操作可见”。


















