计算机在执行程序时,为了进步性能,编译器和处理器经常会对指令重排,个别分为以下三种:

源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存零碎的重排 -> 最终执行指令

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

  • 多线程环境中线程交替执行,因为编译器优化重排的存在,两个线程中应用的变量是否保障一致性是无奈确定的,后果无奈预测。

指令重排——example 1

public void mySort() {    int x = 11;        int y = 12;    x = x + 5;    y = x * x;}

依照失常单线程环境,执行程序是1234。 然而在多线程环境中,可能呈现以下的程序:2134、1324

上述的过程就能够当作指令的重排,即外部执行程序,和咱们的代码程序不一样

然而指令重排也是有限度的,不会呈现上面的程序:4321

因为步骤4,须要依赖y的申明,以及x的申明,故因为存在数据依赖,无奈首先执行

例子

先定义:int a,b,x,y = 0

线程1线程2
x = ay = b
b = 1a = 2

后果x=0,y=0

因为下面的代码,不存在数据的依赖性,因而编译器可能对数据进行重排。

线程1线程2
b = 1a = 2
x = ay = 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的内存可见性)

也就是过在Volatile的写 和 读的时候,退出屏障,防止出现指令重排的。