内存屏障是CPU的顺序承诺而非指令拦截,通过store buffer、invalidation queue、MESI协议等协同实现:LoadLoad确保读完再读,StoreStore确保写完再写,LoadStore确保读完再写,StoreLoad则完成跨核刷存与失效同步。

内存屏障不是“拦截指令”,而是向 CPU 发出的顺序承诺:要求某些访存操作必须在屏障前完成,另一些必须在屏障后开始。它的硬件级效果取决于 CPU 微架构中几个关键组件的协同响应——store buffer、invalidation queue、cache coherence protocol(如 MESI)、以及指令发射调度器。
LoadLoad 屏障:确保“读完再读”
它不阻止读操作本身乱序,而是禁止屏障后的 load 进入加载队列,直到屏障前所有 load 完成数据返回(即已从 cache 或内存拿到有效值)。
- 在 x86 上常编译为
lfence,会清空重排序缓冲区(ROB)中待执行的 load 指令,并等待前面 load 的数据到达寄存器 - 在 ARMv8 上对应
dmb ishld,作用于 inner shareable domain,让后续 load 等待前面 load 的 cache line 状态稳定(比如从 I→S 过渡完成) - 典型触发点:volatile 读之后的普通读(如
if (flag) return data;),防止data读被提前到flag判断之前
StoreStore 屏障:确保“写完再写”
核心目标是让屏障前的 store 提交到 store buffer 并对 cache 可见(至少写入 L1d),避免被屏障后的 store 覆盖或掩盖其顺序语义。
- x86 天然保证 Store-Store 有序,HotSpot 通常省略显式
sfence,仅靠 store buffer FIFO 行为即可满足 JMM - ARMv8 必须插入
dmb ishst,强制将 store buffer 中靠前的条目刷入 L1d cache,同时阻塞后续 store 写入 buffer,直到前序写完成 cache 分配和 tag 更新 - 常见于日志场景:先写日志头(含长度/时间戳),再写日志体;若无 StoreStore,日志体可能先落缓存,头未写完就触发 flush,导致解析失败
LoadStore 屏障:确保“读完再写”
它约束的是读与写之间的相对顺序,防止屏障后的 store 被调度到屏障前的 load 完成之前——这在弱序架构上很常见,x86 默认禁止,所以常被优化掉。
- 硬件动作:暂停 store buffer 的新写入,同时等待前面 load 的地址翻译(TLB 查找)和数据返回均完成
- Java 中主要出现在 volatile 读之后的普通写(如
int x = flag; y = 42;),JVM 插入 LoadStore 防止y = 42被重排到flag读之前 - 若缺失,可能导致写操作基于过期的读结果(例如读配置失败后仍执行旧路径写入)
StoreLoad 屏障:最重的一道关卡
它是唯一跨域全量同步的屏障:既要等 store buffer 刷空(使本核写对其他核可见),又要等 invalidation queue 清空(确保本核已收到其他核的失效通知),才能放行后续 load。
- x86 上用
lock addl $0,(%rsp)实现,本质是带锁的微操作,会序列化 store buffer 并广播 RFO 请求,触发 MESI 状态迁移 - ARMv8 对应
dmb ish,要求所有 store 在 inner shareable domain 内全局可见后,才允许后续 load 执行 - volatile 写后必插——这是保证可见性的物理基础:只有刷出 + 失效同步完成,其他核下次读该地址时才会触发 cache miss 并从主存或最新源加载
- 性能代价最大,因涉及跨核通信与缓存一致性协议交互,在高竞争场景下易成瓶颈
每种屏障都不是独立生效,而是嵌入在 CPU 流水线各阶段的控制信号。它不改变单条指令行为,但重塑了多核间内存操作的可见边界。

















