Java volatile如何实现禁止指令重排
作者:柒 发布时间:2022-01-13 18:29:24
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种:
源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令
单线程环境里面确保最终执行结果和代码顺序的结果一致
处理器在进行重排序时,必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
指令重排 - example 1
public void mySort() {
int x = 11;
int y = 12;
x = x + 5;
y = x * x;
}
按照正常单线程环境,执行顺序是 1 2 3 4
但是在多线程环境下,可能出现以下的顺序:
2 1 3 4
1 3 2 4
上述的过程就可以当做是指令的重排,即内部执行顺序,和我们的代码顺序不一样
但是指令重排也是有限制的,即不会出现下面的顺序
4 3 2 1
因为处理器在进行重排时候,必须考虑到指令之间的数据依赖性
因为步骤 4:需要依赖于 y的申明,以及x的申明,故因为存在数据依赖,无法首先执行
例子
int a,b,x,y = 0
线程1 | 线程2 |
---|---|
x = a; | y = b; |
b = 1; | a = 2; |
x = 0; y = 0 |
因为上面的代码,不存在数据的依赖性,因此编译器可能对数据进行重排
线程1 | 线程2 |
---|---|
b = 1; | a = 2; |
x = a; | y = b; |
x = 2; y = 1 |
这样造成的结果,和最开始的就不一致了,这就是导致重排后,结果和最开始的不一样,因此为了防止这种结果出现,volatile就规定禁止指令重排,为了保证数据的一致性
指令重排 - example 2
比如下面这段代码
public class ResortSeqDemo {
int a= 0;
boolean flag = false;
public void method01() {
a = 1;
flag = true;
}
public void method02() {
if(flag) {
a = a + 5;
System.out.println("reValue:" + a);
}
}
}
我们按照正常的顺序,分别调用method01() 和 method02() 那么,最终输出就是 a = 6
但是如果在多线程环境下,因为方法1 和 方法2,他们之间不能存在数据依赖的问题,因此原先的顺序可能是
a = 1;
flag = true;a = a + 5;
System.out.println("reValue:" + a);
但是在经过编译器,指令,或者内存的重排后,可能会出现这样的情况
flag = true;
a = a + 5;
System.out.println("reValue:" + a);a = 1;
也就是先执行 flag = true后,另外一个线程马上调用方法2,满足 flag的判断,最终让a + 5,结果为5,这样同样出现了数据不一致的问题
为什么会出现这个结果:多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
这样就需要通过volatile来修饰,来保证线程安全性
Volatile针对指令重排做了啥
Volatile实现禁止指令重排优化,从而避免了多线程环境下程序出现乱序执行的现象
首先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:
保证特定操作的顺序保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)
由于编译器和处理器都能执行指令重排的优化,如果在指令键插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说,通过插入内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是刷新出各种CPU的缓存数,因此任何cpu上的线程都能读取到这些数据的最新版本
也就是在Volatile的写和读的时候,加入屏障,防止出现指令重排线程安全得到保证
工作内存与主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决,它们都可以使得一个线程修改后的变量立即对其他线程可见。
对于指令重排导致的可见性问题和有序性问题
可以利用volatile关键字解决,因为volatile的另一个作用就是禁止重排序优化。
来源:https://www.cnblogs.com/qiuwenli/p/13513718.html
猜你喜欢
- java应用CPU有波动,事后怎么分析?目前我采用的方案是根据CPU负载自动执行jstack,并将文件上传到OSS。 环境:阿里云
- 一、C#对XML格式数据的解析1、用XMLDocument来解析XmlDocument xmlDocument = new XmlDocum
- 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静
- 1.java过滤器过滤允许整个项目跨域访问,可通过filter来进行过虑:public class SimpleCORSFilter imp
- Spring bean配置单例或多例模式单例spring bean 默认是单例默认,在对应.xml文件中的配置是:<bean id=&
- 实现比较两个List之间的差异,包括获取两List的差集,交集,并集(不去重&去重)的API解法和优化解法的解决方案。求差集/**
- 最近在搞一个购物车的功能,里面有一个批量删除的操作,采用的是ExpandableListView以及BaseExpandableListAd
- 概述中国特色社会主义乱码问题是我们经常会碰到的问题,解决的办法有很多,本文分别介绍了GET方式和POST方式中文乱码解决方案中一劳永逸的办法
- 浅谈java内存模型 不同的平台,内存模型是不一样的,但是jvm的
- 简介说明本文介绍Spring如何控制Bean注入的顺序。首先需要说明的是:在Bean上加@Order(xxx)是无法控制bean注入的顺序的
- 本文实例讲述了JAVA过滤标签实现将html内容转换为文本的方法。分享给大家供大家参考,具体如下:/*** 把html内容转为文本* @pa
- 前言上一篇已经对线程池的创建进行了分析,了解线程池既有预设的模板,也提供多种参数支撑灵活的定制。本文将会围绕线程池的生命周期,分析线程池执行
- 一.关于使用Mybatisplus自带的selectById和insert方法时的一些问题1.selectById的问题(1).表的主键列名
- 前言在很多时候,我们代码中会有很多分支,而且分支下面的代码又有一些复杂的逻辑,相信很多人都喜欢用 if-else/switch-case 去
- 本文实例讲述了C#移除所有事件绑定的方法。分享给大家供大家参考。具体分析如下:private delegate int DEL_TEST_E
- 最近在项目开发中,使用spring boot+mybatis的架构,数据库设计主键id时定义为bigint类型,使用mybatis的自动生成
- 前言两个数据结构:顺序表和链表数据结构是一门学科,和语言无关。数据 + 结构:一种描述和组织数据的方式。1. 顺序表顺序表是用一段物理地址连
- 问题背景: 我要在一个表单里同时一次性提交多名乘客的个人信息到SpringMVC,前端HTML和SpringMVC Controller里该
- 一、需求场景有时候我们需要在项目中使用一些静态资源文件,比如城市信息文件 countries.xml,在项目启动后读取其中的数据并初始化写进
- CollectionUtils.isNotEmpty()不存在问题org.apache.commons.collections.Collec