代码证明CPU指令重排序

前言

当相邻的两行代码没有关联的时候,CPU可能会进行指令重排序,对程序进行优化执行。

代码

1.以下代码中启了两个线程,线程1执行a=1,x=b,线程2执行b=1,y=a
2.a=1x=b是不相干的两行代码,因此CPU可以对这两个指令进行重排序。同理,b=1y=a也可以指令重排序。
3.假如CPU完全按代码顺序执行,那么可能出现这么几种情况:x=0,y=1,x=1,y=0,x=1,y=1。不可能出现x=0,y=0
4.我们的代码会不断的循环执行,当x=0,y=0的时候退出循环。
4.然而实际情况如下图所示,当程序执行了12万次,出现了x=0,y=0的情况,证实了CPU的指令重排序。
result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
static int a, b, x, y;
public static void main(String[] args){

long time = 0;
while (true) {
time++;
a = 0;b = 0;x = 0;y = 0;
Thread thread1 = new Thread(() -> {
a = 1;
x = b;
});
Thread thread2 = new Thread(() -> {
b = 1;
y = a;
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (Exception e) {}
if (x == 0 && y == 0) {
break;
}
}
System.out.println("time=" + time + ",x=" + x + ",y=" + y);
}
}

总结

指令重排序,可能会给我们的应用造成一定的问题。例如单例模式中的DCL(double check lock),采用了这种加锁模式的代码,如果不设置instance为Volatile,那可能导致线程取到的实例并未完全初始化完毕,可能造成问题。
当线程在new instance的时候,并非原子操作,包括多个步骤,例如给成员变量赋值、将引用指向堆内存的对象,假如这两个步骤发生了指令重排,正好有另一个线程执行的时候,判断instance是否为空,发现不为空,则直接返回该instance,用instance中的未初始化的成员变量去做业务逻辑,那么就可能出现问题。所以Volatile关键字在这里的作用是为了禁止指令重排序,防止线程获取instance的时候,取到的是未赋值完毕的中间态的对象。