我们应用程序的执行,是通过cpu执行各种不同的指令完成的。cpu的速度是比内存要快100倍以上,先看一张图。
因此当cpu从内存load两条不相关的指令时,比如,第一条指令需要向内存写数据,第二条指令只需要在cpu寄存器执行操作就行了,这个时候,因为cpu的优化机制,就会先执行第二条指令,这就是所谓的指令重排。
那么,怎么证明呢?这里用了数学中的反证法,直接上代码!
package com.tml.mouseDemo.core.jvm;
public class DisOrder {
static int a;
static int b;
static int y;
static int x;
public static void main(String[] args) throws InterruptedException {
int count = 0;
while (true) {
a = 0;
b = 0;
y = 0;
x = 0;
Thread t1 = new Thread(() -> {
a = 1;
x = b;
});
Thread t2 = new Thread(() -> {
b = 1;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
count++;
if (x == 0 && y == 0) {
System.out.println("程序发生了指令重排,第" + count + "次");
break;
}
}
}
}
程序很简单,定义了四个静态变量abxy,然后在一个死循环中两个线程分别对这四个变量进行赋值。
假如程序不会发生指令重排,那么a = 1; 一定先于 x = b; 执行,b = 1; 一定先于 y = a; 执行,那么两个线程并发执行的排列组合就是C(4,2)=6
组合的公式参考
这6种组合后,程序的执行结果应该是这样:
可以看到,在假设程序不存在指令重排的基础上,那么xy的结果是不可能出现均为0的情况的。
反证法执行程序后的结果【这个程序执行的次数要看运气,因为多线程本来就是一个随机的事情】
反证的结果与事实不符,假设不成立。也就是,在高级语言中会发生指令重排,这里不仅仅是java会发生,其他高级语言也会发生指令重排。
因为指令重排导致的bug很不好发现,需要有足够的并发冲击才会出现,但是一旦出现,就会造成重大的数据问题。在java中,解决此问题就是使用volatile关键字,禁止重排。