Java volatile可见性本质是JVM与CPU硬件协同:写操作触发MESI状态迁移(S/E→M),广播使其他核缓存行置为Invalid;读操作遇Invalid则强制重载最新值,配合内存屏障禁止重排序。

Java volatile 关键字的可见性,本质上是 JVM 与底层硬件协同工作的结果——它不靠“魔法”,而是通过触发 CPU 缓存一致性协议(主要是 MESI)的状态迁移,并配合内存屏障来实现的。
MESI 协议中的四种状态如何被 volatile 写操作驱动
当一个线程对 volatile 变量执行写操作时,JVM 会生成特定指令,促使该变量所在缓存行(Cache Line)在 CPU 核心本地缓存中发生状态跃迁:
- 从 Shared(S)→ Modified(M):若其他核心也缓存了该变量(即处于 S 状态),当前核心先获得独占权,再修改值;
- 从 Exclusive(E)→ Modified(M):若本核已独占该缓存行(E),可直接修改,无需总线通信;
- Modified(M)触发失效广播:一旦进入 M 状态,CPU 硬件自动向总线发送 Invalidate 请求,使其他所有核心中对应缓存行变为 Invalid(I)状态;
- Invalid(I)迫使后续读取重载:其他线程下次读该 volatile 变量时,发现本地缓存为 I,必须从主内存(或某核心的 M/E 缓存)重新加载最新值。
volatile 读操作如何依赖 MESI 的 Snooping 机制
读 volatile 变量不是简单“查缓存”,而是一次带状态校验的动作:
- CPU 核心在读取前,通过总线嗅探(Snooping)监听其他核心是否发出了 Invalidate 消息;
- 若本地缓存行状态为 I 或已过期,则跳过缓存,直接发起内存请求(通常走 L3 缓存或主内存);
- 若状态为 S 或 E,仍需确认未被其他核心标记为 I —— 这一过程由 MESI 协议硬件自动完成,无需软件干预;
- JVM 在读操作前插入
LoadLoad和LoadStore屏障,确保该读取动作不会被重排到更早的非 volatile 操作之前,从而维持语义顺序。
为什么普通变量无法触发这些状态变化
非 volatile 变量的读写完全由编译器和 CPU 自由优化,不强制关联缓存一致性协议:
立即学习“Java免费学习笔记(深入)”;
- 写操作可能停留在 Store Buffer 中迟迟不提交,缓存行状态长期卡在 E 或 S,不升级为 M,也不广播 Invalidate;
- 读操作可能命中旧缓存副本(S 状态),即使主内存已被更新,也不会主动失效或刷新;
- 没有内存屏障约束,编译器可能把 flag=true 和 a=42 重排序,导致其他线程看到 flag 已变但 a 仍是旧值;
- MESI 协议本身始终运行,但只对“被标记为需要同步”的访问(如 volatile、锁释放/获取)作出响应。
一个典型场景中的状态流转示意
假设两个线程分别运行在 Core0 和 Core1 上,共享 volatile boolean ready = false:
- 初始:Core0 和 Core1 的 ready 缓存行均为 S 状态;
- Core0 执行
ready = true→ 缓存行升为 M,广播 Invalidate → Core1 对应行变为 I; - Core1 执行
if (ready)→ 发现 I 状态 → 触发 Cache Miss → 从 Core0 的 M 行或主内存加载 true; - 整个过程由硬件协议保障,JVM 只需确保生成符合 JMM 要求的指令序列(含屏障),不参与具体缓存行管理。


















