题目:到底啥是内存屏障?到底怎么加的?
难度:medium
标签:java/jmm, difficulty/medium
评分:⭐⭐☆☆☆(2/5)
内存屏障,内存屏障应该是内核的核心指令,控制对象的加载、读取,但具体的我不太了解。Volatile 跟 synchronized 应该都有内存屏障的使用。它们的 Volatile 的可见性和 synchronized 的枷锁原理应该都和内存屏障有关。
优点:
不足:
mfence / lock add / sfence / lfence,ARM 是 dmb 等),不是操作系统内核的概念补充(必背):
四种内存屏障对照表:
| 屏障类型 | x86 指令 | 含义 | 禁止的重排 |
|---|---|---|---|
| LoadLoad | lfence | Load1 ; Barrier ; Load2 | 后续 Load 不能提前到前一个 Load 之前 |
| StoreStore | sfence | Store1 ; Barrier ; Store2 | 后续 Store 不能提前到前一个 Store 之前 |
| StoreLoad | mfence / lock add | Store1 ; Barrier ; Load2 | 后续 Load 不能提前到前一个 Store 之前 |
| LoadStore | x86 天然禁止 | Load1 ; Barrier ; Store2 | 后续 Store 不能提前到前一个 Load 之前 |
JMM 规定的屏障插入位置:
| 位置 | 插入的屏障 | 作用 |
|---|---|---|
| volatile 写前 | StoreStore | 保证写之前所有写操作对后续写都完成 |
| volatile 写后 | StoreLoad | 保证写操作对其他核立即可见 |
| volatile 读前 | LoadLoad | 保证读之后所有操作按序执行 |
| synchronized 解锁后 | StoreStore + StoreLoad | 保证锁内所有操作在解锁前完成,其他线程加锁可见 |
| final 字段构造完成后 | StoreStore + LoadStore | 保证 final 字段初始化后发布给其他线程时值不变 |
x86 vs ARM 内存模型差异:
| 维度 | x86(强内存模型) | ARM(弱内存模型) |
|---|---|---|
| 禁止 LoadStore | 天然禁止 | 需要屏障 |
| 屏障指令 | mfence / sfence / lfence | dmb / dsb / isb |
| volatile 开销 | 大(lock add 锁总线上) | 小(dmb ish) |
| JIT 插入策略 | 少插入,靠硬件 | 多插入,弥补硬件 |
面试杀手锏:
lock 前缀指令,会锁总线——开销大但简单dmb ish,开销小但重排更自由核心考点定位:考察对内存屏障作为 JMM 底层实现机制的精确理解——它是 CPU 指令,由编译器/JIT 插入,用来禁止指令重排和强制缓存同步。
知识点拆解:
lock add;ARM 弱内存模型需要更多屏障,但每条更便宜。JMM 抽象掉了这些差异,Java 代码无需关心Unsafe、Disruptor)中如何绕过 JMM考察方向:
lock 前缀锁总线原理、ARM dmb 指令的具体变体volatile、final、synchronized 的对应关系;与 Unsafe.putOrdered 的部分写入屏障VarHandle 精细控制屏障延伸递进面试题:
lock add;ARM 需要显式插入 dmb 屏障,重排更自由但屏障更便宜面试官您好,内存屏障是CPU 指令层的指令,不是操作系统内核概念,也不是硬件自动插入的。它是 JIT 编译器在特定位置显式插入的指令序列,用来禁止指令重排 + 强制缓存同步。
现代 CPU 有两种优化:CPU 乱序执行(在多个执行单元并行跑指令)和 JIT 重排(编译器改变指令顺序利用数据依赖)。屏障就是在关键位置插一根"栏杆",禁止栏杆两侧的操作被重排到一起。
一共有四种屏障:LoadLoad、StoreStore、LoadStore、LoadStore。每种对应一类禁止的重排,比如 StoreLoad 禁止"后续 Load 提前到前一个 Store 之前"。x86 硬件天然禁止 LoadStore,所以 x86 的屏障开销相对简单——volatile 写只需 lock add 一条指令锁总线;ARM 是弱内存模型,重排更自由,需要显式插入 dmb 屏障。
JMM 规定了屏障插入的位置:volatile 写前插 StoreStore、volatile 写后插 StoreLoad(保证立即刷缓存到主内存)、volatile 读前插 LoadLoad;synchronized 解锁后插 StoreStore + StoreLoad(保证锁内操作在解锁前完成,其他线程加锁可见);final 字段构造完成后插 StoreStore + LoadStore(保证 final 字段初始化后发布时值不变)。
所以 volatile 的可见性 = 内存屏障 + MESI 协议(缓存同步);volatile 的有序性 = 屏障禁止重排;synchronized 的枷锁语义 = 屏障保证锁内操作的可见性。
x86 上 volatile 写是 lock 前缀指令会锁总线,开销大但简单;ARM 上只需 dmb ish,开销小但重排更自由。屏障不是"卡住 CPU",是"禁止重排 + 刷缓存",数据通道照常流动。