volatile禁止指令重排的本质是通过JMM的happens-before规则:对volatile变量的写操作happens-before于后续任意线程对该变量的读操作,从而强制插入内存屏障,约束编译器和处理器不得跨序重排相关读写。

volatile 禁止指令重排,本质不是靠“禁止”本身,而是通过 Happens-Before 原则中的 volatile 规则,在读写之间建立明确的先后约束,从而让 JVM 和处理器必须尊重这个顺序,不能为了优化而打乱。
volatile 规则怎么定义读写顺序?
Java 内存模型(JMM)明确规定:对一个 volatile 变量的写操作 happens-before 于后续任意线程对该变量的读操作。 这个“后续”,指的是在程序逻辑上、时间上发生在写之后的读——不一定是紧挨着,但必须满足该读能看到这次写的值。
它带来两个硬性效果:
- 写操作完成时,该变量最新值必须立即刷新到主内存;
- 读操作开始前,必须从主内存重新加载该变量的值,而不是用寄存器或缓存里的旧副本。
为什么这能“禁止重排”?
所谓禁止重排,并非字面意义的“锁死指令位置”,而是指:编译器和 CPU 不能把一个 volatile 写操作和它之后的任何读/写(尤其是对该 volatile 变量的读)做跨序重排;也不能把一个 volatile 读操作和它之前的任何读/写(尤其是对该 volatile 变量的写)做跨序重排。
典型例子是 DCL 单例构造:
-
mInstance = new ViewModelManager()实际分三步:分配内存 → 初始化对象 → 赋值给引用; - 若
mInstance非 volatile,步骤②③可能被重排为①→③→②,导致其他线程拿到未初始化完成的对象; - 加上 volatile 后,写引用(③)与后续任意读(比如
if (mInstance != null))构成 happens-before 关系,迫使 JVM 插入内存屏障,阻止②③重排,也确保初始化完成才对外可见。
它不保证什么?常见误区
volatile 是轻量级同步机制,有明确边界:
-
不保证原子性:如
counter++(读-改-写三步),即使counter是 volatile,仍可能丢失更新; - 不建立跨变量的顺序约束:对 volatile 变量 A 的写,只对后续读 A 构成 happens-before,对读写其他普通变量 B/C 无直接约束;
- 不替代锁的临界区保护:多个 volatile 变量组合的复合操作(如“先检查状态再更新数据”),仍需 synchronized 或 Lock 保障整体原子性。
和 synchronized 的 happens-before 关系对比
两者都提供 happens-before 保证,但粒度和开销不同:
-
volatile:仅作用于单个变量,读写间建立轻量级顺序,无锁开销; -
synchronized:解锁操作 happens-before 后续同一锁的加锁操作,天然覆盖整个临界区内所有读写,提供更强的一致性保障; - 实际选型看需求:只要求可见+简单顺序控制 → volatile;需要原子执行块或保护多个变量 → synchronized 或更高级工具。

















