Java 内存模型(JMM)- 内存屏障

内存屏障(Memory Barrier)是一种 CPU 或 JVM 级别的指令,用于禁止特定类型的指令重排序强制内存可见性 。Java 内存模型(JMM)定义了四种抽象的屏障类型,它们组合使用来实现 volatilesynchronized 等的语义。


一、四种基本内存屏障

屏障类型 组合 禁止的重排序 典型用途
LoadLoad Load1; LoadLoad; Load2 禁止 Load1Load2 重排序,保证 Load1 先于 Load2 读取内存 volatile 读之后,禁止后续普通读重排到前面
StoreStore Store1; StoreStore; Store2 禁止 Store1Store2 重排序,保证 Store1 的数据先于 Store2 对其他处理器可见 volatile 写之前,保证之前的所有普通写已完成
LoadStore Load1; LoadStore; Store2 禁止 Load1Store2 重排序,保证读取操作先于写入操作 volatile 读之后,禁止后续写重排到前面
StoreLoad Store1; StoreLoad; Load2 全能屏障 :禁止 Store1Load2 重排序,且强制将写缓冲刷到主存,并使其他缓存行失效 volatile 写之后(或 synchronized 释放锁时),保证之前的写对所有后续读可见

其中 StoreLoad 是最"重"的屏障,它兼具其他屏障的大部分效果,开销也最大。


二、StoreStore 屏障详解

StoreStore 屏障的作用:保证屏障之前 的所有普通写操作,都先于 屏障之后的写操作提交到内存(对其他处理器可见)。

volatile 写操作前的应用

根据 JMM 规范,对一个 volatile 变量写入时,会在写入前插入一个 StoreStore 屏障。

java 复制代码
// 普通写
x = 1;
// StoreStore 屏障(保证 x=1 先于下面的 volatile 写)
volatile int v = 2;  // volatile 写

如果没有这个屏障,CPU 可能先执行 v = 2,再执行 x = 1(重排序)。这会导致另一个线程看到 v 被更新了,但 x 还是旧值,破坏了正确性。


三、StoreLoad 屏障详解

StoreLoad 屏障的作用:

  1. 禁止屏障之前 的所有写操作与屏障之后的所有读操作重排序。
  2. 强制将屏障前所有写操作的结果刷回主内存
  3. 并使其他处理器的相应缓存行失效(通过缓存一致性协议)。

volatile 写操作后的应用

对一个 volatile 变量写入后,会插入一个 StoreLoad 屏障。

java 复制代码
volatile int v = 2;  // volatile 写
// StoreLoad 屏障
int y = x;           // 普通读

StoreLoad 保证了 v = 2 对所有处理器立即可见,并且 y = x 不会重排到 v = 2 之前。

注意:在 x86 架构下,volatile 写实际上对应 lock addl 指令,该指令自带 StoreLoad 屏障效果,不额外插入单独的屏障指令。但在 JMM 抽象层面,仍然认为有一个 StoreLoad。


四、其他屏障:LoadLoad 和 LoadStore

1. LoadLoad 屏障

  • 示例:Load1; LoadLoad; Load2
  • 保证 Load1 先于 Load2 完成。
  • 常用于 volatile 读之后,防止后续普通读被重排到 volatile 读之前。

2. LoadStore 屏障

  • 示例:Load1; LoadStore; Store2
  • 保证 Load1 先于 Store2 完成。
  • 用于 volatile 读之后,防止后续普通写被重排到 volatile 读之前。

五、volatile 读写插入屏障的完整策略

操作 插入的屏障 说明
读一个 volatile 变量 1. 读之后插入 LoadLoad 2. 读之后插入 LoadStore 保证后面对普通变量/普通写的访问不会重排到读之前
写一个 volatile 变量 1. 写之前插入 StoreStore 2. 写之后插入 StoreLoad 保证前面的普通写已完成,并让新值立即可见

图示

复制代码
普通写        普通读/写
   ↓
[StoreStore]
   ↓
volatile 写
   ↓
[StoreLoad]   ← 最重,保证可见性和禁止重排序

volatile 读
   ↓
[LoadLoad]
[LoadStore]
   ↓
普通读/写

六、与 CPU 指令的对应(x86 为例)

JMM 屏障 x86 实际指令 说明(x86 是强内存模型)
LoadLoad 无操作 x86 不会重排读-读
LoadStore 无操作 x86 不会重排读-写
StoreStore 无操作 x86 不会重排写-写(除特殊的写组合外)
StoreLoad mfencelock 前缀指令 需要显式屏障,lock addl

因此,在 x86 上,真正生效的只有 StoreLoad 屏障(通过 lock 前缀实现),其他屏障在硬件层面通常不需要做额外事情。但 JMM 为了跨平台统一,依然定义了这四种抽象屏障。


总结

  • StoreStore:保证屏障前的写先于屏障后的写,用于 volatile 写前。
  • StoreLoad:最强屏障,保证写后读的可见性和顺序,用于 volatile 写后。
  • 另外还有 LoadLoadLoadStore,用于 volatile 读后。
  • 不同 CPU 架构对这些屏障的实际实现不同(x86 只有 StoreLoad 需要显式指令)。
相关推荐
月落归舟1 小时前
并发编程之volatile深度解析(二)
java·开发语言·volatile
me8321 小时前
【AI】踩坑LangChain4j集成千问模型:版本适配问题完整解决历程
java·spring·阿里云·ai
来恩10032 小时前
Java Web三大作用域对象
java·开发语言·前端
ゆづき2 小时前
Java 初学者入门指南:常见问题 + 核心知识点 + 进阶 20 道练习题
java·开发语言·学习·算法·水题
_Evan_Yao2 小时前
限流的艺术:令牌桶与滑动窗口的博弈,以及我为何在 AI 项目中选择了后者
java·后端·架构
LIUAWEIO2 小时前
接口 data 满屏反斜杠,怎么展开?
java·开发语言·数据库·json在线解析·data是字符串·json转义·二次json
吴声子夜歌2 小时前
状态机——Spring State Machine
java·后端·spring
XS0301063 小时前
并发编程二
java·开发语言