volatile写插入StoreStore+StoreLoad屏障确保前序写完成且结果对其他线程可见,读插入LoadLoad+LoadStore屏障保证读取最新值且后续操作不重排,二者共同建立happens-before关系。

Java 中 volatile 变量的读写操作会插入特定内存屏障(Memory Barrier),以禁止编译器重排序和 CPU 指令重排序,从而保证可见性和有序性。这些屏障不是由 Java 程序员手动添加的,而是 JVM 在生成字节码或 JIT 编译为本地机器码时,根据 JMM(Java Memory Model)规范自动插入的。
volatile 写操作插入的内存屏障
对 volatile 变量执行写操作(如 x = 1)时,JVM 保证在该写之前的所有普通写和 volatile 写,不能被重排序到该 volatile 写之后;同时,该写的结果必须立即对其他线程可见。为此,JVM 在 volatile 写后插入:
- StoreStore 屏障:确保该 volatile 写(store)之前的任何普通写(store)不会重排序到它之后;
-
StoreLoad 屏障:防止该 volatile 写与后续的 volatile 读/普通读发生重排序(这是开销最大的屏障,在 x86 上通常用
mfence或lock addl $0,(%rsp)实现)。
注意:volatile 写前不插 LoadStore 或 LoadLoad —— 因为写操作本身不依赖读结果,无需限制前面的读。
volatile 读操作插入的内存屏障
对 volatile 变量执行读操作(如 int v = x)时,JVM 保证该读能看到之前所有线程对这个变量的最新写,并且该读之后的普通读/写不能被重排序到它之前。为此,JVM 在 volatile 读前插入:
- LoadLoad 屏障:确保该 volatile 读(load)之前的任何普通读(load)不会重排序到它之后;
- LoadStore 屏障:防止该 volatile 读与后续的普通写(store)重排序。
例如:int a = x; y = 1; 中,若 x 是 volatile,则 y = 1 不会跑到 a = x 前面执行(即使编译器或 CPU 想优化)。
为什么没有单独的 LoadStore 或 StoreLoad 用于所有场景?
内存屏障类型是按「指令对」语义设计的:
-
LoadLoad:两个 load 之间不重排; -
StoreStore:两个 store 之间不重排; -
LoadStore:load 不重排到 store 之后; -
StoreLoad:store 不重排到 load 之前(最严格,影响性能)。
volatile 读需要 LoadLoad + LoadStore,是因为既要保证前面的读已执行完(避免读到过期值),又要防止后面的写“抢跑”;volatile 写需要 StoreStore + StoreLoad,是为了让写生效后再允许后续读,并阻止前面的写被拖后。
不同 CPU 架构的实现差异
JVM 会把抽象屏障映射为具体指令:
-
x86/x64:
LoadLoad、LoadStore、StoreStore是天然满足的(靠处理器内存模型),所以实际只对 volatile 写插入StoreLoad(即mfence或带lock前缀的空操作); -
ARM/AArch64:几乎每条 volatile 读写都需要显式
dmb ish(全内存屏障),因为其弱内存模型更宽松; - JVM 隐藏了这些细节,开发者只需关注 JMM 语义,不必关心底层指令。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











