volatile原理是jvm将其编译为带lock前缀的汇编指令(x86平台),该指令作为隐式全内存屏障,禁止重排序、强制刷缓存并触发mesi协议,确保可见性与有序性,但不保证原子性。

volatile 关键字的原理,核心在于 JVM 将其编译为带 lock 前缀 的汇编指令(x86 平台),而 lock 指令本身就是一个隐式的全内存屏障(Full Memory Barrier),它天然禁止重排序、强制刷写缓存,并触发缓存一致性协议。这不是 Java 层面的“加锁”,而是硬件级同步原语的直接调用。
lock 指令就是内存屏障的硬件落地
在 x86 架构上,JVM 对 volatile 写操作(如 flag = true)生成的汇编,典型形式是:
lock add dword ptr [esp], 0
其中第二条 lock add 是关键——它不改变内存值(+0),但强制执行一个原子的总线/缓存行锁定操作。这个操作等价于插入 StoreLoad 屏障,同时附带以下效果:
- 确保该指令前所有写操作(包括普通变量写)都已提交到缓存或主内存
- 阻塞该指令后所有读操作(包括后续 volatile 读)提前执行
- 将当前 CPU 核心中该变量所在缓存行标记为 Modified,并通过 MESI 协议广播失效信号
- 使其他核心对应缓存行变为 Invalid,下次读必须从主内存加载
volatile 读不发 lock,但靠屏障+协议实现有序加载
volatile 读(如 while (!flag))在 x86 上通常不生成 lock 指令,而是普通 mov,但 JVM 会在其前后插入 LoadLoad 和 LoadStore 屏障:
- LoadLoad 屏障:保证该读不会被重排到它前面的任何读之前 → 防止读到“旧值 + 新 flag”的错乱组合
- LoadStore 屏障:保证该读不会被重排到它后面的任何写之后 → 防止 JIT 把循环判断优化成单次读取
- 虽然没 lock,但每次读都强制绕过本地缓存,依赖 MESI 的 Invalid 状态触发主内存重载
四种屏障如何协同工作
JVM 对 volatile 变量的每次访问都按固定模式插入屏障,不是凭空添加,而是严格对应 JMM 的 happens-before 规则:
-
volatile 写:写操作后插入 StoreStore + StoreLoad
→ 先确保前面所有写完成(StoreStore),再阻断后续读写乱序(StoreLoad) -
volatile 读:读操作前插入 LoadLoad + LoadStore
→ 先确保前面所有读完成(LoadLoad),再防止后面写提前(LoadStore) - StoreLoad 是开销最大、语义最强的屏障,它让 volatile 写成为线程间通信的“同步点”
为什么重排序被禁止了
指令重排序分三类:编译器重排、JIT 重排、CPU 乱序执行。内存屏障从三个层面同时拦截:
- 编译器看到 volatile 修饰,主动禁用对该变量相关指令的优化重排
- JIT 编译时,在字节码生成阶段硬编码插入对应屏障指令(如 lock 或 mfence)
- CPU 执行到 barrier 指令时,暂停流水线,等待屏障前所有内存操作全局可见后,才继续执行屏障后指令










