天天看点

Volatile指令重排

Volatile禁止指令重排

计算机在执行程序时候,为了提高性能,编译器和处理器常常会对编译器进程优化,一般分为以下三种

​ 源代码->编辑器优化的重排–>指令并行的重排->内部系统工单重排–>最终执行指令

单线程环境里面确保最终执行结果和代码顺序的结构一致

处理器在进行重排时候,必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否安保证一致性无法确定,结果无法预测

指令重排的例子

public class ResortSeqDemo {
    int a= 0;
    boolean flag = false;

    public void method01() {
        System.out.println("进去方法一");
        a = 1;
        flag = true;
    }

    public void method02() {
        if(flag) {
            System.out.println("进去方法二");
            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之后,另外的一个线程立马调用方法method02,满足flag的判断,最终执行a+5,结果为5,这样同样会出现数据不一致的问题

出现这个问题的原因是,多线程情况下,线程交替执行,由于编辑器优化重排的存在,两个线程在使用变量能否保证一致性是无法确定的,结果无法预测。这样就需要通过volatile来休息,保证线程的安全性

Volatile针对指令重排做了什么

在字段voaltile针对指令重排做了什么,我们需要先了解一个概念,内存屏障,是一个CPU指令,它的作用有两个

  • 保证特定的操作顺序
  • 保证某些变量内存可见性
Volatile指令重排

由于编译器和处理器都能执行指令重排的优化,如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说,通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障的另外一个作用是刷新各种CPU缓存数,因此任何CPU上的线程都能读取到这些数据的最新版本

内存屏障的作用

  • 禁止指令重排
  • 刷新缓存,任何CPU上的线程都能读取到这些数据的最新版本
  • 也就是在volatile的写和读的时候,加入屏障,防止出现指令重排
线程安全获取保证

工作内存和主内存同步延迟现象导致可见性问题

  • 可以通过synchronise或者volatile关键字解决,他们都可以让一个线程改变的变量立即对其他线程可见

对于指令重排导致的可见性问题和有序性问题

  • 可以使用volatile关键字解决
说明参考

文章为看视频博客学习过程中总结,方便自己以后好复习

https://www.bilibili.com/video/BV18b411M7xz

http://moguit.cn/#/