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禁止指令重排相关推荐

  1. Volatile-3.禁止指令重排

    volatile禁止指令重排 JMM要求有序性 计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3种 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.(单线程 ...

  2. 两个例子详解并发编程的可见性问题和有序性问题,通过volatile保证可见性和有序性以及volatile的底层原理——缓存一致性协议MESI和内存屏障禁止指令重排

    1. 并发编程的可见性问题 2. 并发编程的有序性问题 3. 使用volatile关键字解决可见性问题 4. 可见性问题的本质--缓存不一致 因为cpu执行速度很快,但是内存执行速度相对于CPU很慢, ...

  3. volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))

    volatile是java虚拟机提供的轻量级的同步机制: 1.保证可见性:线程之间可见性(及时通知) 2.不保证原子性 3.禁止指令重排 先了解一下jvm同步 由于JVM运行程序的实体是线程,而每个线 ...

  4. Volatile:可见性保证+禁止指令重排

    Volatile 1.可见性保证 1.1 何为可见性 1.2 JAVA内存模型 1.3 voletile的实现原理 1.4.synchronized 关键字和 volatile 关键字的区别 2.禁止 ...

  5. Java面试之Synchronized无法禁止指令重排却能保证有序性

    为什么Synchronized无法禁止指令重排,却能保证有序性 前言 首先我们要分析下这道题,这简单的一个问题,其实里面还是包含了很多信息的,要想回答好这个问题,面试者至少要知道一下概念: Java内 ...

  6. 一道题决定去留:为什么synchronized无法禁止指令重排,却能保证有序性?

    前几天有一位读者找我问一个问题,说是这道题可能影响了他接下来3年的技术成长. 据说这位读者前面的很多问题会的都还可以,属于那种可过可不过的类型的,面试官出了最后一道题,就是回答的满意就可以给Offer ...

  7. 一道大题决定去留:为什么synchronized无法禁止指令重排,却能保证有序性?

    △Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 253篇原创分享 作者 l Hollis 来源 l Java之道(ID:javaways) 前几天有一位读者找我问一个问题 ...

  8. volatile实现禁止指令重排底层操作原理

  9. volatile指令重排_面试:为了进阿里,重新翻阅了Volatile与Synchro

    面试:为了进阿里,重新翻阅了Volatile与Synchronized 在深入理解使用Volatile与Synchronized时,应该先理解明白Java内存模型 (Java Memory Model ...

最新文章

  1. C# Socket使用代理 及 Http协议、Socks5协议
  2. Linux之Vim的搜索与替换
  3. JavaWeb 命名规则
  4. 怎么在Windows 11中为音频输出选择扬声器
  5. python装饰器使用多吗_如何理解Python装饰器?
  6. 陌屿授权系统v2.0源码
  7. java语言的运行平台,威力加强版
  8. 主要植物叶片数据集农作物病害数据集
  9. Keras中长短期记忆网络LSTM的5步生命周期
  10. SQLyog——下载2058问题
  11. Python 制作动态图
  12. 将Python2代码转换成Python3代码
  13. 再见,马云!再见,世界首富!
  14. Jenkins在搭建过程中遇到的一些问题
  15. STM32闭环步进电机驱动器方案,原理图,源码,PCB
  16. html 适合手机浏览器,六款安卓手机浏览器HTML评测:Q立方浏览器完胜
  17. 移动端怎么实现复制粘贴?
  18. 自学Python,学不会怎么办?
  19. Tudor.CutViewer.Lathe.v2.2.DC250403
  20. node、npm、cnpm踩坑

热门文章

  1. Web3中文|微软:黑客通过Telegram侵害加密公司
  2. 用Python处理文件的实用姿势
  3. 电机控制方式以及驱动选型学习
  4. Android平台上实现银行卡识别(通过阿里云Api-印刷文字识别_银行卡识别)
  5. Revit求直线与平面的交点
  6. 蓝牙防丢器实现安卓的BLE接口编程
  7. 单调注意力相关论文解读
  8. 我关注的一周技术动态 2015.11.08
  9. redis java 队列_Redis 队列 Java调用简单实现
  10. KDD Cup 2022风力发电baseline—使用因果膨胀卷积进行时序预测