volatile禁止指令重排序靠的是在编译器和处理器层面插入内存屏障:写前加StoreStore、写后加StoreLoad、读后加LoadLoad和LoadStore屏障,并通过happens-before规则(写happens-before后续读)约束执行顺序,从而禁止跨volatile边界的重排,但不保证无关指令的顺序。

volatile 禁止指令重排序,靠的是在编译器和处理器层面插入内存屏障(Memory Barrier),不是靠语法糖或运行时检查。
内存屏障强制约束执行顺序
当一个变量被 volatile 修饰后,JVM 会在该变量的读、写操作前后插入特定类型的内存屏障:
- 写 volatile 变量前:插入 StoreStore 屏障,确保它前面的所有普通写操作都已完成,不能重排到它之后
- 写 volatile 变量后:插入 StoreLoad 屏障,阻止它后面的读/写操作被重排到它之前
- 读 volatile 变量后:插入 LoadLoad 和 LoadStore 屏障,保证它后面的操作不会被提到它前面执行
这些屏障直接作用于 CPU 指令流,让编译器和处理器“不敢乱动”相关指令的位置。
重排序有前提,volatile 切断了前提条件
指令重排序只在“不影响单线程语义”的前提下发生(as-if-serial 原则)。而 volatile 变量的读写被 JMM 规定为具有 happens-before 关系:
立即学习“Java免费学习笔记(深入)”;
- 对一个 volatile 变量的写操作,happens-before 于后续对该变量的读操作
- 这个关系不可打破,所以编译器和 CPU 必须保证写操作完成后再允许后续读操作开始,自然就禁止了跨 volatile 边界的重排
比如 flag = true 和 a = 1 这两个无依赖的操作,若 flag 是 volatile,a = 1 就不能被重排到 flag = true 之前执行。
典型场景:双重检查单例中的必要性
new Singleton() 在底层分为三步:
- 分配内存空间
- 初始化对象(调用构造函数)
- 将对象引用赋值给静态变量 instance
后两步可能被重排成:分配内存 → 赋值引用 → 初始化对象。此时另一个线程看到 instance != null,但拿到的是未初始化的对象。加 volatile 后,JVM 会插入屏障,禁止第2步和第3步重排,确保安全发布。
注意:它不阻止所有重排,只管 volatile 相关边界
volatile 不会影响变量自身内部的指令顺序,也不限制与它无关的其他普通变量之间的重排。它的作用范围是“以 volatile 读写为锚点”的指令边界。比如两个普通变量 x、y 的赋值之间仍可能重排,即使它们出现在 volatile 写之前——除非你用 synchronized 或显式屏障控制。


















