3.Volatile禁止指令重排

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种:

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

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

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

指令重排——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 = 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的内存可见性)

http://image.moguit.cn/8444be76f9224cea9268d955d70711eb

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

你可能感兴趣的