目录
- 1. 四种基本内存屏障
- 1.1 StoreStore屏障
- 1.2 StoreLoad屏障
- 1.3 LoadLoad屏障
- 1.4 LoadStore屏障
- 2. 内存屏障在volatile中的具体应用
- 2.1 volatile写操作的内存屏障插入
- 2.2 volatile读操作的内存屏障插入
- 3. 内存屏障如何保证happens-before关系
- 4. 实际处理器中的实现差异
- 5. 示例分析
- 6. 为什么需要四种屏障
- 7. 总结
- 详细特性对比
- 实际效果示例对比
- 不同处理器架构上的表现
在Java中,volatile
关键字是一种轻量级的同步机制,用于确保变量的可见性和有序性。为了实现这些功能,Java虚拟机(JVM)在底编程层使用了内存屏障(Memory Barrier),这些内存屏障确保了在多线程环境下,对共享变量的读写操作的正确顺序和可见性。
内存屏障(Memory Barrier)是处理器提供的一种指令,用于控制指令执行顺序和内存可见性。在Java中,volatile关键字就是通过插入内存屏障来实现其内存语义的。下面我将详细解释四种内存屏障的含义和工作原理。
1. 四种基本内存屏障
1.1 StoreStorhttp://www.devze.come屏障
作用:
- 确保屏障前的所有普通写操作(store)完成并刷新到主内存
- 在屏障后的volatile写操作之前执行
生效机制:
普通写操作1 普通写操作2 StoreStore屏障 volatile写操作
实际效果:保证在volatile变量写入前,所有之前的普通变量写入都已经完成并可见
1.2 StoreLoad屏障
作用:
- 确保屏障前的所有写操作(包括volatile写)完成并刷新到主内存
- 在屏障后的所有读操作(包括volatile读)之前执行
生效机制:
volatile写操作 StoreLoad屏障 volatile读操作/普通读操作
实际效果:这是最"重量级"的屏障,会使该屏障之前的所有内存访问指令(存储和装载)完成之后,才执行该屏障之后的内存访问指令
1.3 LoadLoad屏障
作用:
- 确保屏障前的所有读操作(load)完成
- 在屏障后的所有读操作之前执行
生效机制:
volatile读操作 LoadLoad屏障 普通读操作/volatile读操作
实际效果:保证在读取后续变量前,先完成对volatile变量的读取
1.4 LoadStore屏障
作用:
- 确保屏障前的所有读操作(load)完成
- 在屏障后的所有写操作之前执行
生效机制:
volatile读操作 LoadStore屏障 普通写操作/volatile写操作
实际效果:保证在写入任何变量前,先完成对volatile变量的读取
2. 内存屏障在volatile中的具体应用
2.1 volatile写操作的内存屏障插入
编译器会在volatile写操作前后插入以下屏障:
[普通写操作] StoreStore屏障 [volatile写操作] StoreLoad屏障
示例:
x = 42; // 普通写 y = true; // volatile写
实际生成的指令序列:
store x, 42 StoreStore屏障 store y, true StoreLoad屏障
2.2 volatile读操作的内存屏障插入
编译器会在volatile读操作前后插入以下屏障:
LoadLoad屏障 [volatile读操作] LoadStore屏障
示例:
if (y) { // volatile读 z = x; // 普通读和普通写 }
实际生成的指令序列:
LoadLoad屏障 load y LoadStore屏障 load x store z, x
3. 内存屏障如何保证happens-before关系
内存屏障通过限制处理器和编译器的重排序来建立happens-before关系:
- StoreStore屏障:确保volatile写之前的普通写操作happens-before volatile写
- StoreLoad屏障:确保volatile写happens-before后续的volatile读/写
- LoadLoad屏障:确保volatile读happens-before后续的所有读操作
- LoadStore屏障:确保volatile读happens-before后续的所有写操作
4. 实际处理器中的实现差异
不同处理器架构对内存屏障的支持不同:
- x86/64:原生支持较强的内存模型,只有StoreLoad屏障是真正有作用的
- ARM/PowerPC:需要显式使用所有四种屏障
- JVM:会根据目标平台将Java内存屏障映射到具体的处理器指令
例如,在x86上:
- StoreStore屏障通常实现为空操作(no-op)
- StoreLoad屏障实现为
mfence
指令或lock
前缀指令
5. 示例分析
class ReorderingExample { int x = 0; volatile boolean v = false; void writer() { x = 42; // 普通写 v = true; // volatile写 } void reader() { if (v) { // volatile读 System.out.println(x); // 普通读 } } }
内存屏障插入后的执行顺序保证:
- 在writer()中:
x = 42
和v = true
之间插入StoreStore屏障- 确保x的写入在v的写入前完成并可见
- 在reader()中:
if (v)
前插入LoadLoad屏障System.out.println(x)
前插入LoadStore屏障- 确保读取v后才读取x,且读取的是最新值
6. 为什么需要四种屏障
四种屏障对应不同的读写组合,提供了细粒度的控制:
- StoreStore:写→写顺序
- StoreLoad:写→读顺序(最常用且开销最大)
- LoadLoad:读→读顺序
- LoadStore:读→写顺序
这种细编程客栈粒度控制允许JVM在不同架构上实现最优性能,只在必要的地方插入必要的屏障。
7. 总结
四种内存屏障共同作用,确保了:
- volatile写的可见性(StoreStore + StoreLoad)
- volatile读的 freshness(LoadLoad + LoadStore)
- 防止不合理的重排序
- 建立正确的happens-before关系
以下是四种内存屏障的详细对比表格,展示了它们的特点、作用和区别:
屏障类型 | 插入位置 | 保证的操作顺序 | 主要作用 | 典型使用场景 | 开销级别 |
---|---|---|---|---|---|
StoreStore | volatile写操作之前 | 普通写 → volatile写 | 确保volatile写之前的所有普通写操作对其它处理器可见 | volatile写前的普通变量写入 | 低 |
StoreLoad | volatile写操作之后 | volatile写 → 后续所有读 | 确保volatile写对所有处理器可见后,才能执行后续的读操作 | volatile写后可能的读操作 | 高 |
LoadLoad | volatile读操作之前 | volatile读 → 后续所有读 | 确保先完成volatile读,才能进行后续的读操作 | volatile读后的普通变量读取 | 中 |
LoadStore | volatile读操作之后 | volatile读 → 后续所有写 | 确保先完成volatile读,才能进行后续的写操作 | volatile读后的普通变量写入 | 中 |
详细特性对比
特性 | StoreStore | StoreLoad | LoadLoad | LoadStore |
---|---|---|---|---|
防止的重排序类型 | 写-写重排序 | 写-读重排序 | 读-读重排序 | 读-写重排序 |
保证的可见性 | 使屏障前的写对所有线程可见 | 使屏障前的写对所有线程可见 | 确保读取最新值 | 确保基于最新值进行写入 |
对应CPU指令 | 通常为no-op(x86)sfence(某些架构) | mfence(x86)sync(PowerPC) | lfence(某些架构) | 通常组合使用 |
发生频率 | 每次volatile写前 | 每次volatile写后 | 每次volatile读前 | 每次volatile读后 |
影响范围 | 仅影响写操作顺序 | 影响写后所有读操作 | 仅影响读操作顺序 | 影响读后所有写操作 |
性能影响 | 较小 | 较大 | 中等 | 中等 |
实际效果示例对比
屏障类型 | 代码示例 (屏障位置) | 保证的效果 |
---|---|---|
StoreStore | x=1; [SS]; v=2; | 其他线程看到v=2时,必定能看到x=1 |
StoreLoad | v=1; [SL]; if(x)... | 执编程客栈行x的读取时,v=1的写入已经全局可见 |
LoadLoad | [LL]; if(v)...; tmp=x; | 读取x时,v的读取已经完成且是最新值 |
LoadStore | if(v)...; [LS]; x=1; | 写入x=1时,已经基于最新的v值进行了判断 |
不同处理器架构上的表现
屏障类型 | x86/64实现 | ARM实现 | PowerPC实现 |
---|---|---|---|
StoreStore | 通常不需要(隐式保证) | dmb ishst | lwsync |
StoreLoad | mfence指令 | dmb ish | sync |
LoadLoad | 通常不需要(隐式保证) | dmb ishld | lwsync |
LoadStore | 通常不需要(隐式保证) | dmb ish | lwsync |
这个表格总结了四种内存屏障的关键区别,理解这些差异对于编写正确的高性能并发程序非常重要。实际开发中,虽然我们很少直接操作这些屏障(它们由JVM自动插入),但了解其原理有助于诊断并发问题和优化性能。
理解这些内存屏障的工作原理,有助于深入理解Java内存模型和并发编程中的各种可见性、有序性问题。
到此这篇关于Java volatile 内存屏障详解:四种内存屏障的作用与生效机制的文章就介绍到这了,更多javascript相关Java volatile 内存屏障内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论