指令重排的前提:
1、单线程中执行结果如果一致,那么可以任意交换。
2、语句之间没有依赖关系,那么可以交换。
指令重排的原因:
为了提高执行效率。
理解:
a=1;
a++;
b=2;
a=1这条指令相当于我发一个请求给内存,然后内存将1这个值返回给a。可以拆解为两步。那么当我发送请求给内存,内存还没有返回数据给我的时候,这个时候,我是处于等待的状态的。那我是不是可以去执行其他的操作,比如b=2;然后当内存将1返回给我的时候,我才完成a=1的操作。然后再执行a++。这样执行的顺序就是b=2;a=1;a++;得到的结果跟a=1;a++;b=2;完全一致。但是指令重排后,JVM的运行效率提高了。
案例1:
Thread one = new Thread(() -> {
a = 1;
x = 2;
});
这种情况下,a和x并没有依赖关系,且无论如何交换得到的结果都是一致的。因此可能会先执行x=2,再执行a=1。
案例2:
Thread one = new Thread(() -> {
a = 1;
a++;
});
这种情况下,a=1与a++存在依赖关系,则不会发生指令重排。
案例3:
public class Test {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; ; i++) {
x = 0; y = 0;
a = 0; b = 0;
Thread one = new Thread(() -> {
a = 1;
x = b;
});
Thread other = new Thread(() -> {
b = 1;
y = a;
});
one.start(); other.start();;
one.join(); other.join();
if (x == 0 && y == 0) {
String result = "第" + i + "次(" + x + ", " + y + ")";
System.out.println(result);
}
}
}
}
因为线程one中,a和x并不存在依赖关系,因此可能会先执行x=b;而这个时候,b=0。因此x会被赋值为0,而a=1这条语句还没有被执行的时候,线程other先执行了y=a这条语句,这个时候a还是a=0;因此y被赋值为了0。所以存在情况x=0;y=0。这就是指令重排导致的多线程问题。
解决方法:
我们可以采用volatile修饰,防止指令重排。这个时候,永远不会存在x=0,y=0的情况。
public class Test {
private volatile static int x = 0, y = 0;
private volatile static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; ; i++) {
x = 0; y = 0;
a = 0; b = 0;
Thread one = new Thread(() -> {
a = 1;
x = b;
});
Thread other = new Thread(() -> {
b = 1;
y = a;
});
one.start(); other.start();;
one.join(); other.join();
if (x == 0 && y == 0) {
String result = "第" + i + "次(" + x + ", " + y + ")";
System.out.println(result);
}
}
}
}