volatile变量的内存可见性由JVM与CPU硬件协同实现:写操作通过lock前缀指令触发MESI缓存一致性协议,使其他核心对应缓存行失效;读操作因缓存失效而强制从主存或最新缓存加载,从而保证可见性。

volatile 变量的内存可见性,本质上不是 Java 虚拟机单方面保证的,而是由 JVM 指令生成 + 底层 CPU 硬件协同完成的。关键就在写操作时插入的 lock 前缀指令——它触发了硬件级同步机制,而具体实现方式取决于 CPU 架构和数据所处位置:要么锁总线,要么锁缓存。
为什么需要总线锁或缓存锁?
多核 CPU 各自拥有私有高速缓存(L1/L2),共享主内存。当两个线程分别在不同核心上读写同一个变量时,可能各自缓存了旧值,彼此看不到更新——这就是缓存不一致问题。要让修改“立刻被别人看到”,必须打破缓存隔离,强制刷新与同步。
解决思路有两个层级:
- 粗粒度方案:锁住整个系统总线,让其他 CPU 暂时无法访问内存——简单但开销大;
- 细粒度方案:只锁定当前变量所在缓存行(cache line),通过协议通知其他核心作废对应缓存副本——高效且主流。
总线锁:早期通用但低效的方式
在老式 CPU 或某些特殊场景(如变量未命中缓存、跨缓存行访问)下,lock 指令会向总线发出 LOCK# 信号,暂停其他 CPU 对内存的访问。此时所有对主存的读写都被阻塞,直到当前写操作完成并刷新到内存。
这种机制能确保原子性和可见性,但代价明显:
- 其他 CPU 即使访问完全无关的内存地址,也会被阻塞;
- 总线成为性能瓶颈,尤其在高并发场景下;
- 现代 x86 处理器已基本避免使用纯总线锁,除非缓存不可用。
缓存锁:现代 CPU 的默认选择(MESI 协议驱动)
当前主流处理器(Intel/AMD)在缓存命中的前提下,lock 指令不会锁总线,而是启用“缓存锁定”:将变量所在缓存行标记为独占(Exclusive)或已修改(Modified),并通过 MESI 协议广播状态变更。
MESI 四种状态控制缓存一致性:
- M(Modified):该缓存行已被修改,与主存不一致,且仅存在于当前 CPU 缓存中;
- E(Exclusive):该缓存行干净、未被修改,且仅存在于当前 CPU 缓存中;
- S(Shared):该缓存行干净,可能存在于多个 CPU 缓存中;
- I(Invalid):该缓存行无效,不能使用,需重新从内存或其它缓存加载。
当 volatile 写发生时:
- CPU 将目标缓存行置为 M 状态,并写回主存(或延迟回写,但协议保证可见);
- 通过总线嗅探(bus snooping),其他 CPU 发现该地址被修改,立即将自己缓存中对应行设为 I 状态;
- 下次读取该 volatile 变量时,因缓存失效,必须重新从主存(或拥有最新值的缓存)加载——从而看到最新值。
volatile 读写的硬件效果总结
volatile 读本身不加 lock,但它依赖缓存一致性协议的被动响应:
- 读操作会触发缓存行加载,若之前被标记为 I,则强制从主存或其它缓存同步;
- 写操作显式插入 lock 前缀,触发缓存行独占获取 + 状态广播 + 强制刷新;
- 二者结合,确保“一个线程写,另一个线程读一定能见到最新值”,即内存可见性;
- 同时,lock 指令天然带有内存屏障语义,禁止编译器和 CPU 对其前后的读写指令重排序。

















