Volatile禁止指令重排
Volatile禁止指令重排
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种:
源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令
单线程环境里面确保最终执行结果和代码顺序的结果一致
处理器在进行重排序时,必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
指令重排 - 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
比如下面这段代码
/*** @author HuangHaiyang* @date 2020/08/18* @description: description* @version: 1.0.0*/
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关键字的另一个作用就是禁止重排序优化
举例
package com.qcby.设计模式.single;/*** @author HuangHaiyang* @date 2020/07/07* @description: description* @version: 1.0.0*/
public class LazySafe {private LazySafe(){}//对象加上了volatile关键字是为了保证变量的可见性,防止指令重排序//第二个线程拿到的可能是半实列化的对象,所以要加volatile防止指令重排序private volatile static LazySafe lazySafe;public static LazySafe getInstance(){if(lazySafe!=null){//双重判定synchronized (LazySafe.class){if(lazySafe!=null){lazySafe=new LazySafe();}}}return lazySafe;}
}
DCL(双端检锁)机制不一定是线程安全的,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因是在某一个线程执行到第一次检测的时候,读取到 instance 不为null,instance的引用对象可能没有完成实例化。因为 instance = new SingletonDemo();可以分为以下三步进行完成:
- memory = allocate(); // 1、分配对象内存空间
- instance(memory); // 2、初始化对象
- instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null
但是我们通过上面的三个步骤,能够发现,步骤2 和 步骤3之间不存在 数据依赖关系,而且无论重排前 还是重排后,程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
- memory = allocate(); // 1、分配对象内存空间
- instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null,但是对象还没有初始化完成
- instance(memory); // 2、初始化对象
这样就会造成什么问题呢?
也就是当我们执行到重排后的步骤2,试图获取instance的时候,会得到null,因为对象的初始化还没有完成,而是在重排后的步骤3才完成,因此执行单例模式的代码时候,就会重新在创建一个instance实例
指令重排只会保证串行语义的执行一致性(单线程),但并不会关系多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,这就造成了线程安全的问题
所以需要引入volatile,来保证出现指令重排的问题,从而保证单例模式的线程安全性
Volatile禁止指令重排相关推荐
- Volatile-3.禁止指令重排
volatile禁止指令重排 JMM要求有序性 计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3种 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.(单线程 ...
- 两个例子详解并发编程的可见性问题和有序性问题,通过volatile保证可见性和有序性以及volatile的底层原理——缓存一致性协议MESI和内存屏障禁止指令重排
1. 并发编程的可见性问题 2. 并发编程的有序性问题 3. 使用volatile关键字解决可见性问题 4. 可见性问题的本质--缓存不一致 因为cpu执行速度很快,但是内存执行速度相对于CPU很慢, ...
- volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))
volatile是java虚拟机提供的轻量级的同步机制: 1.保证可见性:线程之间可见性(及时通知) 2.不保证原子性 3.禁止指令重排 先了解一下jvm同步 由于JVM运行程序的实体是线程,而每个线 ...
- Volatile:可见性保证+禁止指令重排
Volatile 1.可见性保证 1.1 何为可见性 1.2 JAVA内存模型 1.3 voletile的实现原理 1.4.synchronized 关键字和 volatile 关键字的区别 2.禁止 ...
- Java面试之Synchronized无法禁止指令重排却能保证有序性
为什么Synchronized无法禁止指令重排,却能保证有序性 前言 首先我们要分析下这道题,这简单的一个问题,其实里面还是包含了很多信息的,要想回答好这个问题,面试者至少要知道一下概念: Java内 ...
- 一道题决定去留:为什么synchronized无法禁止指令重排,却能保证有序性?
前几天有一位读者找我问一个问题,说是这道题可能影响了他接下来3年的技术成长. 据说这位读者前面的很多问题会的都还可以,属于那种可过可不过的类型的,面试官出了最后一道题,就是回答的满意就可以给Offer ...
- 一道大题决定去留:为什么synchronized无法禁止指令重排,却能保证有序性?
△Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 253篇原创分享 作者 l Hollis 来源 l Java之道(ID:javaways) 前几天有一位读者找我问一个问题 ...
- volatile实现禁止指令重排底层操作原理
- volatile指令重排_面试:为了进阿里,重新翻阅了Volatile与Synchro
面试:为了进阿里,重新翻阅了Volatile与Synchronized 在深入理解使用Volatile与Synchronized时,应该先理解明白Java内存模型 (Java Memory Model ...
最新文章
- C# Socket使用代理 及 Http协议、Socks5协议
- Linux之Vim的搜索与替换
- JavaWeb 命名规则
- 怎么在Windows 11中为音频输出选择扬声器
- python装饰器使用多吗_如何理解Python装饰器?
- 陌屿授权系统v2.0源码
- java语言的运行平台,威力加强版
- 主要植物叶片数据集农作物病害数据集
- Keras中长短期记忆网络LSTM的5步生命周期
- SQLyog——下载2058问题
- Python 制作动态图
- 将Python2代码转换成Python3代码
- 再见,马云!再见,世界首富!
- Jenkins在搭建过程中遇到的一些问题
- STM32闭环步进电机驱动器方案,原理图,源码,PCB
- html 适合手机浏览器,六款安卓手机浏览器HTML评测:Q立方浏览器完胜
- 移动端怎么实现复制粘贴?
- 自学Python,学不会怎么办?
- Tudor.CutViewer.Lathe.v2.2.DC250403
- node、npm、cnpm踩坑