• 欢迎访问本站,本站记录博主日常编程遇到的问题,知识,惊奇软件等。如有问题还请留言


    Deprecated: strip_tags(): Passing null to parameter #1 ($string) of type string is deprecated in /www/wwwroot/gschaos.club/wp-content/themes/Git-alpha-6h0SRk/header.php on line 294

既然CPU有缓存一致性协议(MESI),为什么JMM还需要volatile关键字?

java mysticalycc 3周前 (08-01) 22次浏览 已收录 0个评论
文章目录[隐藏]

🧠 为什么 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 xchglock 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,最终实现程序语义上的“可见性”与“顺序性”保证

MysticalYcc , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:既然CPU有缓存一致性协议(MESI),为什么JMM还需要volatile关键字?
喜欢 (0)
mysticalycc
关于作者:
简短的个人签名

Warning: Attempt to read property "comment_author_email" on null in /www/wwwroot/gschaos.club/wp-content/themes/Git-alpha-6h0SRk/comments.php on line 47
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到