volatile 系列之指令重排序导致的可见性问题

什么是指令重排序呢?为了更加直观地理解,老司机还是通过一个案例来说明
 


public class MemoryReorderingExample {private static int x=0,y=0:private static int a=0,b=0;public static void main(String[] args) throws InterruptedException {int i=0;while(true){i++;x=0;y=0;a=0;b=0;Thread t1=new Thread(()->{a=1;x=b;});Thread t2=new Thread(()->{b=1    y=a;});t1.start();t2.start();t1.join();t2.join();String result="第"+i+"次("+x+","+y+")";if(x==0&&y==0){System.out.println(result);break;}}}
}


上面这段程序的逻辑如下:

  • 定义四个int类型的变量,初始化都为0。
  • 定义两个线程t1、t2,t1 线程修改a和x的值,t2 线程修改 b 和 y  的值,分别启动两个线程
  • 正常情况下,x和y的值,会根据 t1 和 t2 线程的执行情况来决定。
  1. 如果t1线程优先执行,那么得到的结果是x=0、y=1。
  2. 如果t2线程优先执行,那么得到的结果是x=1、y=0。
  3. 如果t1和t2线程同时执行,那么得到的结果是x=1、y=1。

我们来看一下运行结果:

第136161次(0,0)


        大家看到这个结果是不是大吃一惊?在运行了13万次之后,竟然得到一个 x=0、y=0 的结果
        其实这就是所谓的指令重排序问题,假设上面的代码通过指令重排序之后,变成下面这种结构

Thread t1=new Thread(()->{x=b;  //指令重排序a=1;
});
Thread t2=new Thread(()->(y=a;  //指令重排序b=1;
});


经过重排序之后,如果 t1 和 t2 线程同时运行,就会得到x=0、y=0的结果,这个结果从人的视角来看,就有点类似于t1线程中 a=1 的修改结果对 t2 线程不可见,同样 t2 线程中 b=1 的执结果对t1线程不可见。


什么是指令重排序


        指令重排序是指编译器或 CPU 为了优化程序的执行性能而对指令进行重新排序的一种手段重排序会带来可见性问题,所以在多线程开发中必须要关注并规避重排序。
        从源代码到最终运行的指令,会经过如下两个阶段的重排序
        第一阶段,编译器重排序,就是在编译过程中,编译器根据上下文分析对指令进行重排序目的是减少CPU和内存的交互,重排序之后尽可能保证CPU从寄存器或缓存行中读取数据。在前面分析JIT优化中提到的循环表达式外提(Loop Expression Hoisting)就是编译器层面的重排序,从CPU层面来说,避免了处理器每次都去内存中加载stop,减少了处理器和内存的交互开销。

if(!stop){while(true){i++;}
}


        第二阶段,处理器重排序,处理器重排序分为两个部分

  • 并行指令集重排序,这是处理器优化的一种,处理器可以改变指令的执行顺序。
  • 内存系统重排序,这是处理器引入Store Buffer 缓冲区延时写入产生的指今执行顺序不一致的问题,在后续内容中会详细说明。

        为了帮助读者理解,笔者专门针对并行指令集的原理做一个简单的说明。

        什么是并行指令集?在处理器内核中一般会有多个执行单元,比如算术逻辑单元、位移单元等。在引入并行指令集之前,CPU在每个时钟周期内只能执行单条指令,也就是说只有一个执行单元在工作,其他执行单元处于空闲状态;在引入并行指令集之后,CPU在一个时钟周期内可以同时分配多条指令在不同的执行单元中执行。
        那么什么是并行指令集的重排序呢?如图所示,假设某一段程序有多条指令,不同指令的执行实现也不同。对于一条从内存中读取数据的指令,CPU 的某个执行单元在执行这条指令并等到返回结果之前,按照CPU 的执行速度来说它足够处理几百条其他指令,而 CPU为了提高执行效率,会根据单元电路的空闲状态和指令能否提前执行的情况进行分析,把那些指令地址顺序靠后的指令提前到读取内存指令之前完成。
        实际上,这种优化的本质是通过提前执行其他可执行指令来填补 CPU的时间空隙,然后在结束时重新排序运算结果,从而实现指令顺序执行的运行结果。
 

讲到这里,大家先消化一下,思考一下,后面还有哪里不足的请指出来,我也好改正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/219486.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

排序算法之一:直接插入排序

1.基本思想 直接插入排序是一种简单的插入排序法,其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 实际中我们玩扑克牌时,就用…

【UML】组件图中的供接口和需接口与面向对象中的接口

UML(统一建模语言)组件图中的“供接口”(Provided Interface)和“需接口”(Required Interface)与面向对象编程中的接口概念有关联,但它们在应用上有所区别。 下面解释两者的关系: …

NCNN 源码学习【一】:学习顺序

最近一段时间一直在做模型部署的工作,主要是利用NCNN部署到安卓端,跟着网上的博客和开源项目,做了很多工作,也学习到很多东西,但是对于NCNN的源码,并没有仔细的研究过,对我来说,仿佛…

C++共享和保护——(2)生存期

归纳编程学习的感悟, 记录奋斗路上的点滴, 希望能帮到一样刻苦的你! 如有不足欢迎指正! 共同学习交流! 🌎欢迎各位→点赞 👍 收藏⭐ 留言​📝 生命如同寓言,其价值不在于…

改进YOLOv8注意力系列二:结合CBAM、Coordinate Attention、deformable_LKA_Attention可变形大核注意力

改进YOLOv8注意力系列二:结合ACmix、Biformer、BAM注意力机制 代码CBAM注意力Coordinate Attention坐标注意力deformable_LKA_Attention可变形大核注意力加入方法各种yaml加入结构本文提供了改进 YOLOv8注意力系列包含不同的注意力机制以及多种加入方式,在本文中具有完整的代…

C语言之文件操作(上)

C语言之文件操作(上) 文章目录 C语言之文件操作(上)1. 什么是⽂件?1.1 程序⽂件1.2 数据⽂件1.3 ⽂件名 2. ⼆进制⽂件和⽂本⽂件3. ⽂件的打开和关闭3.1 流和标准流3.1.1 流3.1.2 标准流 4. ⽂件指针5. 文件的打开与关…

【广州华锐视点】物流数字孪生三维可视化系统打造更高效、智能的物流管理体验

在当今快速发展的物流行业中,传统的管理和监控方法往往难以满足复杂运营的需求。为了解决这个问题,广州华锐互动提供物流数字孪生三维可视化系统定制开发服务,打造更为高效、智能的物流管理体验。 物流数字孪生三维可视化系统是一种基于虚拟现…

虚拟现实三维电子沙盘数字沙盘开发教程第5课

虚拟现实三维电子沙盘数字沙盘无人机倾斜摄影全景建模开发教程第5课 设置system.ini 如下内容 Server122.112.229.220 userGisTest Passwordchinamtouch.com 该数据库中只提供 成都市火车南站附近的数据请注意,104.0648,30.61658 在鼠标指定的位置增加自己的UI对象&…

【Hive】——DDL(CREATE TABLE)

1 CREATE TABLE 建表语法 2 Hive 数据类型 2.1 原生数据类型 2.2 复杂数据类型 2.3 Hive 隐式转换 2.4 Hive 显式转换 2.5 注意 3 SerDe机制 3.1 读写文件机制 3.2 SerDe相关语法 3.2.1 指定序列化类(ROW FORMAT SERDE ‘’) 3.2.2 指定分隔符&#xff0…

深入理解Dubbo-8.Dubbo的失败重试设计

👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理🔥如果感觉博主的文章还不错的话&#xff…

鸿蒙(HarmonyOS)北向开发项目编译问题汇总

运行Hello World Hello World 工程可以运行在模拟器中,或者运行在真机设备中。本示例先以选择将 Hello World 工程运行在模拟器中进行说明,如果选择运行在真机设备中,需要先对工程进行签名,然后才能运行在真机设备中。 DevEco S…

从零到一:influxdb时序性数据库的基本概念与操作指南

目录 ​编辑 引言 数据库(database) 创建数据库 删除数据库 进入数据库 展示influxdb中所有数据库 测量(measurement) 写入测量 展示测量 总结 引言 InfluxDB是一个开源的时序数据库,专门设计用于处理时间序列数据。它是由InfluxD…

C# 两个日期比较大小

文章目录 C# 两个日期比较大小直接比较大小工具类DateTime.Compare C# 两个日期比较大小 直接比较大小 string ed "2023-12-13 09:27:59.000";//过去式DateTime nowDateTime DateTime.Now;DateTime expirationDate Convert.ToDateTime(ed);//质保期 长日期DateT…

一行奇异代码,解决transition过渡动画无效问题!

一行奇异代码,解决transition过渡动画无效问题! 无效的transition过渡动画 你是否遇到过这种情况:在css中设置了transition过渡动画,但使用时,确无效。 例如以下代码,便是一例: 在此代码中&a…

【云原生kubernets】Service 的功能与应用

一、Service介绍 在kubernetes中,pod是应用程序的载体,我们可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,这也就意味着不方便直接采用pod的ip对服务进行访问。为了解决这个问题,kubernetes提供了Service资…

慎用,Mybatis-Plus这个方法可能导致死锁

1 场景还原 1.1 版本信息 MySQL版本:5.6.36-82.1-log Mybatis-Plus的starter版本:3.3.2 存储引擎:InnoDB1.2 死锁现象 A同学在生产环境使用了Mybatis-Plus提供的 com.baomidou.mybatisplus.extension.service.IService#saveOrUpdate(T, co…

Linux中使用podman管理容器

本章主要介绍使用podman管理容器 了解什么是容器,容器和镜像的关系安装和配置podman拉取和删除镜像给镜像打标签导出和导入镜像创建和删除镜像数据卷的使用管理容器的命令使用普通用户管理容器 对于初学者来说,不太容易理解什么是容器,这里…

挑战与创新:光学字符识别技术在处理复杂表格结构中的应用

OCR(Optical Character Recognition)光学字符识别技术是指通过计算机软硬件将印刷或手写的字符转化为可编辑和搜索的文本。这项技术已经被广泛应用于各个领域,例如扫描文档、自动化数据输入、图书数字化等。但是,当涉及到处理复杂…

“ABCD“[(int)qrand() % 4]作用

ABCD[(int)qrand() % 4] 作用 具体来说: qrand() 是一个函数,通常在C中用于生成一个随机整数。% 4 会取 qrand() 生成的随机数除以4的余数。因为4只有四个不同的余数(0, 1, 2, 3),所以这实际上会生成一个0到3之间的随…

java方法引用语法规则以及简单案例

目录 一、方法引用1.1 什么是方法引用1.2 方法引用的语法规则1.3 构造器引用1.4 方法引用的简单案例 参考资料 一、方法引用 1.1 什么是方法引用 方法引用是 Lambda 表达式的一种简写形式,用于表示已有方法的直接引用。 类似于lambda表达式,方法引用也…