🧠 为什么 mesi 协议并不足以保证 java 中的“可见性”语义?
1. mesi 是硬件层级的缓存一致性,volatile
是语言层级的可见性保证
- mesi 主要解决“同一物理地址在多核缓存中的副本一致性问题”;
- 根据mesi,CPU某核(假设CPU0)的缓存行(包含变量x)是M S 或E的时候,如果总线嗅探到了变量x被其其他核(比如CPU1)执行了写操作(remote write)那么CPU0中的该缓存行会置为I(无效),在CPU0后续对该变量执行读操作的时候,发现是I状态,就会去主存中同步最新的值(其实由于L3缓存的存在,这里也可能是直接从L3同步到CPU0的L1和L2缓存,而不直接访问主存)。
- 但实际可能不太理想,因为在CPU1执行写操作,要等到其他CPU(比如CPU0 CPU2 )将对应缓存行置为I状态,然后再将数据同步到主存,这个写操作才能完成 由于这样性能较差所以引入了Store Buffer,CPU1只需要将数据写入到Store Buffer,而不等待其他CPU把缓存行状态置为I,就开始忙别的去了 等到其他CPU通知CPU1我们都知道那个缓存失效啦,然后这个数据才同步到主存。
- java 的内存模型(JMM)中,变量的值可能因为编译器优化、CPU 重排序、寄存器缓存等行为,在程序执行语义上出现“不可见”的现象;
- JMM 必须定义 happens-before 关系 来确保
volatile
的写-读间具备“先行发生”的效果,而不是仅靠 mesi 本身。
2. Store Buffer 的引入,彻底打破了“写后立即可见”的直觉
- 现代 CPU 为了写入性能,引入了 Store Buffer 机制,即写操作先写入 buffer,并异步刷新至 L1/L2/L3/主存;
- 这意味着即使某 CPU 核心已经写入某变量,其他核心也可能读不到更新后的值;
- 而
volatile
写操作会在底层编译为带有 lock 前缀的指令(如lock xchg
或lock addl
),这些指令会触发LOCK#
信号,刷新 store buffer 并使缓存一致性生效。
3. lock 指令不仅保证可见性,还强制禁止重排序
volatile
写操作不仅要求“写入立即对其他线程可见”,还要求写前的操作不能被重排序到写后;- jvm 会在 volatile 写前插入一个 StoreStore Barrier,在读后插入 LoadLoad + LoadStore Barrier;
- 而 lock 前缀指令天然具备屏障效应(full memory fence),强制指令前后的语义不被 CPU 重排;
- lock 前缀的汇编指令会强制写入主存,也可避免前后指令的CPU重排序,并及时让其他核中的相应缓存行失效,从而利用mesi达到符合预期的效果。
- 非lock前缀的汇编指令在执行写操作的时候,可能是是不生效的 比如前面所说的Store Buffer的存在,lock前缀的指令在功能上可以等价于内存屏障,可以让其立即刷入主存。
- 所以说:volatile = 禁止重排 + 刷新 store buffer + 触发 mesi 一致性
4. java 层面 volatile
的等价语义:
从 jvm 规范角度来看,volatile
在底层汇编中可抽象为如下三个效果:
volatile 操作 | 对应行为 |
---|---|
写 volatile | Store Barrier → 写 → flush 到主存(lock 指令) |
读 volatile | 读 → Load Barrier(确保之后不会读取过期数据) |
其他指令交错 | 禁止与 volatile 操作发生重排序(通过屏障) |
5. 如果只有 mesi,没有 volatile
会怎样?
想象下面的代码:
boolean flag = false;
Thread A:
data = 42;
flag = true;
Thread B:
if (flag) {
System.out.println(data);
}
在没有 volatile
的情况下,flag = true
可能先于 data = 42
被刷新(重排序),导致线程 B 看到 flag = true
,但读到 data = 0
。
mesi 并不会限制这种重排序,它只处理“缓存行数据一致性”,不保证执行语义顺序。
✅ 总结:JMM 与 mesi 是两个维度的“协同体系”
层级 | 作用 |
---|---|
mesi(硬件) | 负责缓存一致性,数据层面的一致同步 |
Store Buffer | 为提高写性能,导致“写延迟生效”问题 |
lock 指令 | 刷新 Store Buffer,触发 mesi,防止乱序 |
volatile(语言) | 结合汇编指令、内存屏障和 JMM,最终实现程序语义上的“可见性”与“顺序性”保证 |