当前位置: 首页 > article >正文

volatile之四类内存屏障指令 内存屏障 面试重点 底层源码

目录

volatile 两大特性

可见性

有序性

总结

什么是内存屏障

四个 CPU 指令

四大屏障

重排

重排的类型

为什么会有重排?

线程中的重排和可见性问题

如何防止重排引发的问题?

总结

happens-before 和 volatile 变量规则

内存屏障指令 写操作

volatile 写操作前有一个 storestore 屏障

volatile 写操作后有一个 storeload 屏障

内存屏障指令 读操作

volatile 读操作后有一个loadload屏障和一个 loadstore 屏障


volatile 两大特性

可见性

在多线程环境下,每个线程可能会维护自己的本地缓存(例如 CPU 缓存或者线程私有的缓存),因此一个线程对 volatile 变量的修改对其他线程是立即可见的。

有序性

在多线程环境下,每个线程可能会维护自己的本地缓存(例如 CPU 缓存或者线程私有的缓存),因此一个线程对 volatile 变量的修改对其他线程是立即可见的。

  • 当一个线程修改了 volatile 变量的值,其他线程可以看到这个修改,因为 volatile 变量的更新会直接刷新到主内存中(而不是线程本地的缓存)。
  • 缓存一致性:Java 内存模型 (JMM) 保证了写入 volatile 变量会直接更新主内存,并且读取时会从主内存中获取数据,从而确保了其他线程能够及时看到变量的最新值。

总结

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。

当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量

所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。

什么是内存屏障

Java 操作内存的后门

四个 CPU 指令

加载 和 存储

loadload()

storestore()

loadstore()

storeload()


四大屏障

屏障类型是 loadload

屏障类型是 storestore

屏障类型是 loadstore

屏障类型是 loadstore

重排

在多线程编程中,重排(Reordering)是指编译器、CPU 或 JVM 在执行程序时,出于性能优化的目的,改变了代码执行的顺序,但不改变程序的最终行为(如果没有数据依赖关系的话)。这通常是在指令级别或者内存访问顺序上发生的。

重排的类型

  1. 指令重排(Instruction Reordering):
    • 编译器可能会改变语句的执行顺序,但保证语义不变。
    • 例如,在循环中,如果没有数据依赖关系,编译器可以将某些计算移到循环外部执行。
  1. 内存重排(Memory Reordering):
    • CPU 或缓存系统可能会改变内存操作的顺序,从而影响多个线程对共享变量的访问顺序。
    • 这可能会导致不同步的线程看到的数据状态不一致,尤其是在没有适当同步的情况下。

为什么会有重排?

重排的主要目的是提高程序的执行效率。例如,编译器或处理器可能将某些不依赖的操作交换顺序,从而减少等待时间或提高并行性。然而,如果不小心,这也可能导致并发程序中的可见性问题,即线程看到的数据不一致,或者多线程的执行顺序与我们期望的不一致。

线程中的重排和可见性问题

在多线程程序中,重排的危险通常与线程之间的内存可见性执行顺序有关。举个简单的例子:

boolean flag = false;

Thread t1 = new Thread(() -> {
    flag = true;   // A
});

Thread t2 = new Thread(() -> {
    if (flag) {    // B
        System.out.println("Flag is true");
    }
});

t1.start();
t2.start();

在这个例子中,如果没有适当的同步机制,t2 可能会比 t1 先执行,即 t2 可能在 flag 变量还没有被 t1 更新时就执行了。如果处理器或编译器对内存访问进行了重排,t2 可能看到的是 flagfalse,即使 t1 已经把它设置为 true。这是因为在不同线程间的内存访问可能没有同步,导致重排影响了线程间的可见性。

如何防止重排引发的问题?

  1. 使用 volatile 关键字
    • volatile 关键字能够防止对变量的重排序,也确保每个线程都能看到变量的最新值。
    • 它告诉 JVM 和 CPU,所有的线程都应该直接从主内存中读取该变量的值,而不是从缓存中读取。
java


复制编辑
private volatile boolean flag = false;
  1. 使用同步(synchronizedLock
    • synchronized 能够保证同一时刻只有一个线程执行某个代码块,并且它还可以保证内存的可见性,即写入的值对其他线程可见。
    • 通过使用 synchronized 或显式的 Lock,可以避免并发环境中的重排序和可见性问题。
  1. 使用 final 关键字
    • 对于一些常量(即初始化后不再修改的变量),使用 final 可以避免变量被重排,确保多线程读取时不会遇到问题。

总结

重排是为了优化性能而发生的一种行为,但在多线程环境中,若没有正确的同步机制,重排可能导致程序行为不一致,尤其是在共享数据的访问上。为了防止这类问题,我们通常会使用 volatilesynchronized 或其他同步机制来保证多线程程序的正确性。

如果你有更多具体的多线程问题,或者有其他相关概念不太明白,随时可以继续问我!

happens-before 和 volatile 变量规则

只要是第一个操作是 voliatile 读 都不能重排

只要是第二个操作是 voliatile 写 都不能重排

voliatile 读写 后 voliatile 读写 都不难重排

内存屏障指令 写操作

volatile 写操作前有一个 storestore 屏障

volatile 写操作后有一个 storeload 屏障

内存屏障指令 读操作

volatile 读操作后有一个loadload屏障和一个 loadstore 屏障


http://www.kler.cn/a/522281.html

相关文章:

  • 元素的显示与隐藏
  • Nginx前端后端共用一个域名如何配置
  • 代码随想录算法训练营第三十八天-动态规划-完全背包-279.完全平方数
  • 使用numpy自定义数据集 使用tensorflow框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预
  • AI 浪潮席卷中国年,开启科技新春新纪元
  • JavaScript正则表达式
  • 多模态论文笔记——TECO
  • 已解决:Win10任务状态栏卡死点击无响应的解决方案
  • 【SAP-PP】生产订单和计划订单
  • DeepSeek-R1试用
  • AI在Facebook平台中的安全应用探索
  • JAVA 接口、抽象类的关系和用处 详细解析
  • Python-基础环境(01) 虚拟环境,Python 基础环境之虚拟环境,一篇文章助你完全搞懂!
  • 通过案例研究二项分布和泊松分布之间关系(2)
  • Lucene中DocValues 和 Stored Fields 的用法
  • 【Unity3D】Tilemap俯视角像素游戏案例
  • 字节启动AGI长期研究计划,代号Seed Edge
  • Nacos未授权新建用户漏洞(/nacos/v1/auth/users)
  • 深入理解JWT及其应用
  • 简易CPU设计入门:控制总线的剩余信号(一)
  • 粒子群算法 笔记 数学建模
  • 真正理解std::move
  • < OS 有关 > 阿里云 几个小时前 使用密钥替换 SSH 密码认证后, 发现主机正在被“攻击” 分析与应对
  • 关于Dubbo的面试题概念原理配置及代码
  • 【信息系统项目管理师-选择真题】2012上半年综合知识答案和详解
  • 十三先天记