java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java volatile 内存屏障

Java volatile四种内存屏障的作用与生效机制原理详解

作者:亲爱的非洲野猪

内存屏障是处理器提供的一种指令,用于控制指令执行顺序和内存可见性,在Java中,volatile关键字就是通过插入内存屏障来实现其内存语义的,下面我将详细解释四种内存屏障的含义和工作原理,感兴趣的朋友一起看看吧

在Java中,volatile关键字是一种轻量级的同步机制,用于确保变量的可见性和有序性。为了实现这些功能,Java虚拟机(JVM)在底层使用了内存屏障(Memory Barrier),这些内存屏障确保了在多线程环境下,对共享变量的读写操作的正确顺序和可见性。

内存屏障(Memory Barrier)是处理器提供的一种指令,用于控制指令执行顺序和内存可见性。在Java中,volatile关键字就是通过插入内存屏障来实现其内存语义的。下面我将详细解释四种内存屏障的含义和工作原理。

1. 四种基本内存屏障

1.1 StoreStore屏障

作用

生效机制

普通写操作1
普通写操作2
StoreStore屏障
volatile写操作

实际效果:保证在volatile变量写入前,所有之前的普通变量写入都已经完成并可见

1.2 StoreLoad屏障

作用

生效机制

volatile写操作
StoreLoad屏障
volatile读操作/普通读操作

实际效果:这是最"重量级"的屏障,会使该屏障之前的所有内存访问指令(存储和装载)完成之后,才执行该屏障之后的内存访问指令

1.3 LoadLoad屏障

作用

生效机制

volatile读操作
LoadLoad屏障
普通读操作/volatile读操作

实际效果:保证在读取后续变量前,先完成对volatile变量的读取

1.4 LoadStore屏障

作用

生效机制

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关系:

  1. StoreStore屏障:确保volatile写之前的普通写操作happens-before volatile写
  2. StoreLoad屏障:确保volatile写happens-before后续的volatile读/写
  3. LoadLoad屏障:确保volatile读happens-before后续的所有读操作
  4. LoadStore屏障:确保volatile读happens-before后续的所有写操作

4. 实际处理器中的实现差异

不同处理器架构对内存屏障的支持不同:

例如,在x86上:

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); // 普通读
        }
    }
}

内存屏障插入后的执行顺序保证

6. 为什么需要四种屏障

四种屏障对应不同的读写组合,提供了细粒度的控制:

  1. StoreStore:写→写顺序
  2. StoreLoad:写→读顺序(最常用且开销最大)
  3. LoadLoad:读→读顺序
  4. LoadStore:读→写顺序

这种细粒度控制允许JVM在不同架构上实现最优性能,只在必要的地方插入必要的屏障。

7. 总结

四种内存屏障共同作用,确保了:

以下是四种内存屏障的详细对比表格,展示了它们的特点、作用和区别:

屏障类型插入位置保证的操作顺序主要作用典型使用场景开销级别
StoreStorevolatile写操作之前普通写 → volatile写确保volatile写之前的所有普通写操作对其它处理器可见volatile写前的普通变量写入
StoreLoadvolatile写操作之后volatile写 → 后续所有读确保volatile写对所有处理器可见后,才能执行后续的读操作volatile写后可能的读操作
LoadLoadvolatile读操作之前volatile读 → 后续所有读确保先完成volatile读,才能进行后续的读操作volatile读后的普通变量读取
LoadStorevolatile读操作之后volatile读 → 后续所有写确保先完成volatile读,才能进行后续的写操作volatile读后的普通变量写入

详细特性对比

特性StoreStoreStoreLoadLoadLoadLoadStore
防止的重排序类型写-写重排序写-读重排序读-读重排序读-写重排序
保证的可见性使屏障前的写对所有线程可见使屏障前的写对所有线程可见确保读取最新值确保基于最新值进行写入
对应CPU指令通常为no-op(x86)
sfence(某些架构)
mfence(x86)
sync(PowerPC)
lfence(某些架构)通常组合使用
发生频率每次volatile写前每次volatile写后每次volatile读前每次volatile读后
影响范围仅影响写操作顺序影响写后所有读操作仅影响读操作顺序影响读后所有写操作
性能影响较小较大中等中等

实际效果示例对比

屏障类型代码示例 (屏障位置)保证的效果
StoreStorex=1; [SS]; v=2;其他线程看到v=2时,必定能看到x=1
StoreLoadv=1; [SL]; if(x)...执行x的读取时,v=1的写入已经全局可见
LoadLoad[LL]; if(v)...; tmp=x;读取x时,v的读取已经完成且是最新值
LoadStoreif(v)...; [LS]; x=1;写入x=1时,已经基于最新的v值进行了判断

不同处理器架构上的表现

屏障类型x86/64实现ARM实现PowerPC实现
StoreStore通常不需要(隐式保证)dmb ishstlwsync
StoreLoadmfence指令dmb ishsync
LoadLoad通常不需要(隐式保证)dmb ishldlwsync
LoadStore通常不需要(隐式保证)dmb ishlwsync

这个表格总结了四种内存屏障的关键区别,理解这些差异对于编写正确的高性能并发程序非常重要。实际开发中,虽然我们很少直接操作这些屏障(它们由JVM自动插入),但了解其原理有助于诊断并发问题和优化性能。

理解这些内存屏障的工作原理,有助于深入理解Java内存模型和并发编程中的各种可见性、有序性问题。

到此这篇关于Java volatile 内存屏障详解:四种内存屏障的作用与生效机制的文章就介绍到这了,更多相关Java volatile 内存屏障内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文