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

 更新时间:2025年09月10日 11:15:36   作者:亲爱的非洲野猪  
内存屏障是处理器提供的一种指令,用于控制指令执行顺序和内存可见性,在Java中,volatile关键字就是通过插入内存屏障来实现其内存语义的,下面我将详细解释四种内存屏障的含义和工作原理,感兴趣的朋友一起看看吧

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

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

1. 四种基本内存屏障

1.1 StoreStore屏障

作用

  • 确保屏障前的所有普通写操作(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关系:

  1. StoreStore屏障:确保volatile写之前的普通写操作happens-before volatile写
  2. StoreLoad屏障:确保volatile写happens-before后续的volatile读/写
  3. LoadLoad屏障:确保volatile读happens-before后续的所有读操作
  4. 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 = 42v = true之间插入StoreStore屏障
    • 确保x的写入在v的写入前完成并可见
  • 在reader()中:
    • if (v)前插入LoadLoad屏障
    • System.out.println(x)前插入LoadStore屏障
    • 确保读取v后才读取x,且读取的是最新值

6. 为什么需要四种屏障

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

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

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

7. 总结

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

  • volatile写的可见性(StoreStore + StoreLoad)
  • volatile读的 freshness(LoadLoad + LoadStore)
  • 防止不合理的重排序
  • 建立正确的happens-before关系

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

屏障类型插入位置保证的操作顺序主要作用典型使用场景开销级别
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 内存屏障内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 应用启动数据初始化接口CommandLineRunner和Application详解

    应用启动数据初始化接口CommandLineRunner和Application详解

    这篇文章主要介绍了应用启动数据初始化接口CommandLineRunner和Application详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java面试题 从源码角度分析HashSet实现原理

    Java面试题 从源码角度分析HashSet实现原理

    这篇文章主要介绍了Java面试题 从源码角度分析HashSet实现原理?,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • SpringBoot2.3集成ELK7.1.0的示例代码

    SpringBoot2.3集成ELK7.1.0的示例代码

    这篇文章主要介绍了SpringBoot2.3集成ELK7.1.0的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • SpringBoot整合quartz实现定时任务

    SpringBoot整合quartz实现定时任务

    这篇文章主要为大家详细介绍了SpringBoot如何整合quartz实现定时任务,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-10-10
  • Java8 Collectors求和功能的自定义扩展操作

    Java8 Collectors求和功能的自定义扩展操作

    这篇文章主要介绍了Java8 Collectors求和功能的自定义扩展操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Java设计模式中的适配器模式

    Java设计模式中的适配器模式

    这篇文章主要介绍了Java设计模式中的适配器模式, 适配器模式是将一个类的接口适配成用户所期待的,一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中,需要的朋友可以参考下
    2024-01-01
  • 深入理解Java @Entity注解及其与数据库表的关联

    深入理解Java @Entity注解及其与数据库表的关联

    这篇文章主要介绍了深入理解Java @Entity注解及其与数据库表的关联,@Entity注解在JPA中占据核心地位,它建立起Java实体类和数据库表之间的映射关系,需要的朋友可以参考下
    2025-05-05
  • 浅谈Java编程之if-else的优化技巧总结

    浅谈Java编程之if-else的优化技巧总结

    说实话,其实我很讨厌在代码里大量使用if-else,一是因为该类代码执行方式属于面向过程的,二嘛,则是会显得代码过于冗余.这篇笔记,主要记录一些自己在工作实践当中针对if-else的优化心得,将会不定期地长期更新,需要的朋友可以参考下
    2021-06-06
  • IDEA在plugins里搜不到mybatisx插件的解决方法

    IDEA在plugins里搜不到mybatisx插件的解决方法

    本文主要介绍了IDEA在plugins里搜不到mybatisx插件的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • JAVA面试题 从源码角度分析StringBuffer和StringBuilder的区别

    JAVA面试题 从源码角度分析StringBuffer和StringBuilder的区别

    这篇文章主要介绍了JAVA面试题 从源码角度分析StringBuffer和StringBuilder的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面我们来一起学习下吧
    2019-07-07

最新评论