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