Java中内存屏障在volatile和sychronized的应用
一、内存屏障核心概念
内存屏障(Memory Barrier) 是CPU指令,用于控制指令重排序和内存可见性。在Java中,JMM(Java内存模型)通过四种屏障保证并发安全:
屏障类型 | 作用 | 示例指令 |
---|---|---|
LoadLoad | 禁止该屏障前后的读操作重排序 | Load1; LoadLoad; Load2 |
StoreStore | 禁止该屏障前后的写操作重排序 | Store1; StoreStore; Store2 |
LoadStore | 禁止前面的读操作与后面的写操作重排序 | Load; LoadStore; Store |
StoreLoad | 禁止前面的写操作与后面的读操作重排序(全能屏障,开销最大) | Store; StoreLoad; Load |
二、volatile 的内存屏障实现
1. volatile 写操作
public class VolatileExample {private volatile int flag = 0;public void writer() {flag = 1; // volatile写}
}
屏障插入策略:
在写操作前插入 StoreStore屏障
→ 确保普通写先于volatile写刷新到主存在写操作后插入 StoreLoad屏障
→ 确保volatile写立即全局可见
2. volatile 读操作
public void reader() {if (flag == 1) { // volatile读// do something}
}
屏障插入策略:
在读操作前插入 LoadLoad屏障
→ 禁止volatile读与后续普通读重排序在读操作后插入 LoadStore屏障
→ 禁止volatile读与后续普通写重排序
三、synchronized 的内存屏障实现
1. 进入同步块(monitorenter)
public synchronized void syncMethod() {// 临界区
}
屏障插入:
在进入同步块前插入 LoadLoad + LoadStore屏障
→ 保证获取锁时能看到最新数据隐式包含 acquire语义:
禁止临界区内的读写操作重排序到锁外
2. 退出同步块(monitorexit)
public synchronized void syncMethod() {// 临界区结束
} // 退出同步块
屏障插入:
在退出同步块后插入 StoreStore + StoreLoad屏障
→ 保证修改全局可见隐式包含 release语义:
禁止临界区内的读写操作重排序到锁释放后
四、volatile vs synchronized 屏障对比
特性 | volatile | synchronized |
---|---|---|
屏障类型 | 显式屏障 | 隐式屏障(JVM自动插入) |
写操作屏障 | StoreStore + StoreLoad | StoreStore + StoreLoad |
读操作屏障 | LoadLoad + LoadStore | LoadLoad + LoadStore |
原子性 | 单操作原子(如long/double) | 块级原子 |
重排序限制 | 禁止volatile与普通操作重排序 | 禁止临界区内外操作重排序 |
开销 | 低(仅内存屏障) | 高(锁获取/释放+屏障) |
五、总结
Q:内存屏障在volatile和synchronized中起什么作用?
A:
内存屏障通过两种机制保障并发安全:
禁止指令重排序:
volatile:
写操作前插入StoreStore屏障(禁止普通写与volatile写重排序)
写操作后插入StoreLoad屏障(确保写立即全局可见)
读操作前插入LoadLoad屏障(禁止volatile读与普通读重排序)
读操作后插入LoadStore屏障(禁止volatile读与普通写重排序)
synchronized:
锁获取时插入LoadLoad+LoadStore屏障(保证看到最新数据)
锁释放时插入StoreStore+StoreLoad屏障(保证修改全局可见)
保证内存可见性:
volatile写屏障强制刷新写缓冲区到主存
volatile读屏障使本地缓存失效(强制从主存读取)
synchronized的释放屏障相当于volatile写
synchronized的获取屏障相当于volatile读
Q:volatile和synchronized的内存语义有何异同?
A:
相同点:
都依赖内存屏障实现可见性和有序性
写操作都包含StoreStore+StoreLoad屏障
读操作都包含LoadLoad+LoadStore屏障
不同点:
原子性:
volatile仅保证单操作的原子性(如long写)
synchronized保证块级原子性
屏障范围:
volatile仅保护特定变量
synchronized保护整个临界区
实现机制:
volatile直接操作内存屏障
synchronized通过锁的获取/释放隐式触发屏障
六、进阶问题总结
StoreLoad屏障为什么开销最大?
需要等待前面所有写操作刷新到主存
使本核缓存失效(可能触发缓存回填)
在x86中对应
mfence
指令(全内存屏障)
volatile能替代synchronized吗?
// 错误示例:volatile无法保证复合操作原子性 private volatile int count = 0; public void increment() {count++; // 实际是 read-modify-write 三步操作 }
volatile无法保证多操作的原子性(如i++)
synchronized通过互斥解决复合操作原子性
什么是happens-before?与屏障的关系?
volatile写 happens-before 后续volatile读
synchronized解锁 happens-before 后续加锁
内存屏障是实现happens-before的底层机制