计算机在执行程序时,为了进步性能,编译器和处理器经常会对指令重排,个别分为以下三种:
源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存零碎的重排 -> 最终执行指令
处理器在进行重排序时必须要思考指令之间的数据依赖性。
- 多线程环境中线程交替执行,因为编译器优化重排的存在,两个线程中应用的变量是否保障一致性是无奈确定的,后果无奈预测。
指令重排——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的内存可见性)
也就是过在Volatile的写 和 读的时候,退出屏障,防止出现指令重排的。