volatile写操作通过lock指令触发mesi协议,将本核缓存行置为m状态并广播invalidate使其他核缓存失效;读操作因缓存缺失被迫从主内存或m态核重新加载最新值,从而保证可见性。

volatile 并不“强行触发主内存刷盘”,它也不直接控制数据是否写入物理主存。它的作用是借助 CPU 硬件机制,让修改对其他核“尽快可见”,而这个过程依赖 MESI 协议 + lock 指令 + 内存屏障的协同,不是简单地“把值立刻写进 RAM”。
volatile 写操作如何激活 MESI 状态流转
当线程写一个 volatile 变量时,JVM 会插入带 lock 前缀 的汇编指令(如 lock xadd 或 lock addl $0, (%rsp)),这触发两个关键动作:
- CPU 将该变量所在缓存行(Cache Line)状态从 E(独享)或 S(共享)强制设为 M(已修改),标记为“脏”
- 通过总线嗅探(Bus Snooping)广播 invalidate 请求,通知其他核:你们缓存里同一地址的副本全部置为 I(无效)状态
为什么其他核能“马上看到新值”
其他线程下次读这个 volatile 变量时,CPU 检查本地缓存行状态:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 若为 I(无效),必须触发 remote read——从主内存(或拥有 M 状态的核的缓存)重新加载最新值
- 这个加载过程天然绕过 Store Buffer(因为 volatile 写已清空本核 Store Buffer),避免了因缓冲导致的延迟可见
- 读操作还插入 LoadLoad 和 LoadStore 屏障,确保不会把后续读写重排到它前面
所谓“刷主内存”其实是被动结果,不是主动目标
MESI 协议本身不要求立即回写主存。M 状态的缓存行可以停留很久,直到被替换或显式刷新。但 volatile 的实际效果常表现为“近似立即刷盘”,原因在于:
- lock 指令隐含 StoreLoad 屏障,强制本核 Store Buffer 刷空,使 M 状态缓存行更早进入可回写队列
- 一旦该缓存行被其他核请求(例如因 cache miss 触发 shared 请求),拥有 M 状态的核必须先将数据写回主存(或直传给请求方),才能转为 S 状态
- 高竞争场景下,频繁的 invalidate 请求会加速 M→S→E 的流转,间接推动数据落地
普通变量为什么做不到这点
普通 int 写操作只是更新本地缓存,状态可能长期停留在 S 或 E,不触发广播,也不清 Store Buffer:
- 其他核缓存仍为 S,继续读旧值,哪怕主存已被更新(但没被强制同步)
- 即使某次写最终落盘,中间存在不可控延迟窗口,JMM 不保证这个时间点
- 没有内存屏障,编译器和 CPU 都可能重排序,进一步破坏观察顺序
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










