volatile不直接插入内存屏障,而是由jvm依jmm规范自动为读写操作添加对应屏障:写操作加storestore+storeload,读操作加loadload+loadstore,确保相关操作顺序并建立happens-before关系。

Java 中 volatile 关键字本身不直接插入内存屏障指令,而是由 JVM 根据 Java 内存模型(JMM)规范,在编译和运行时自动为 volatile 变量的读写操作插入特定类型的内存屏障,从而禁止某些方向的指令重排序。它不阻止所有重排,只约束与该 volatile 变量直接相关的读写操作顺序。
volatile 写操作插入 StoreStore + StoreLoad 屏障
当对一个 volatile 变量执行写操作时,JVM 会在该写指令之后插入两类内存屏障:
- StoreStore 屏障:确保该写之前的所有普通写操作(如对非 volatile 字段的赋值),不会被重排到这个 volatile 写之后;
- StoreLoad 屏障:确保该写之后的所有读或写操作,不会被重排到这个 volatile 写之前。
例如:
num = 2; // 普通写<br> ready = true; // volatile 写 → 此处插入 StoreStore + StoreLoad
这样就保证了
num = 2 一定在 ready = true 之前完成,并对其他线程可见,避免“状态发布早于初始化完成”的问题。
volatile 读操作插入 LoadLoad + LoadStore 屏障
当读取一个 volatile 变量时,JVM 会在该读指令之前插入两类屏障:
- LoadLoad 屏障:确保该读之前的所有普通读操作已完成(即不会让后续读提前);
- LoadStore 屏障:确保该读之后的所有写操作,不会被重排到该读之前。
例如:
if (ready) { // volatile 读 → 此处插入 LoadLoad + LoadStore<br>
r = num + num; // 这行不会被重排到 if 判断之前<br>
}
一旦看到
ready == true,就能安全读取 num 的最新值——前提是写端也用 volatile 发布,且两者构成 happens-before 关系。
屏障作用贯穿整个执行链路
内存屏障的效果不是单一层级的限制,而是由编译器、JIT 编译器和 CPU 共同遵守:
- 编译器(javac / JIT):生成字节码时避开跨屏障的指令移动;逃逸分析、字段冗余消除等优化遇到 volatile 字段时会自动禁用;
-
CPU 硬件层面:x86 上常用
lock addl $0,0(%rsp)或mfence实现;ARM 等弱内存序架构则使用dmb ish等更严格的 barrier 指令。
这些底层机制共同保障 volatile 语义的正确性,但开发者无需也不应手动干预屏障类型或位置。
volatile 不禁止的重排序类型
需要特别注意,volatile 并非万能,以下情况仍可能发生重排序:
- 两个非 volatile 变量之间的读写顺序无法靠 volatile 保障;
- 复合操作(如
count++)仍存在竞态条件,因为 volatile 不提供原子性; - 与 volatile 变量无 happens-before 关系的操作,其顺序不受约束。
也就是说,volatile 只约束“自身读写”及其紧邻的相关操作,不建立全局执行顺序。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











