一.什么是重排序
我们先不用管概念,先看现象,后面会总结,用自顶向下的思维去理解概念。直接看代码演示现象。
1.1 代码演示重排序
public class OutOfOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for (; ; ) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
//用CountDownLatch是为了两个线程能够同时执行
CountDownLatch latch = new CountDownLatch(3);
Thread one = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.countDown();
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
two.start();
one.start();
latch.countDown();
one.join();
two.join();
String result = "第" + i + "次(" + x + "," + y + ")";
//当出现x=0,y=0时说明出现了重排序现象,出现了就break跳出死循环
if (x == 0 && y == 0) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
}
}
}
1.2 分析
//线程一
a = 1;
x = b;
//线程二
b = 1;
y = a;
从以上代码分析可知,按照我们常规的想法,会出现三种不同的执行顺序和最终的结果:
1.a=1;x=b(0);b=1;y=a(1),最终结果是x=0,y=1
2.b=1;y=a(0);a=1;x=b(1),最终结果是x=1,y=0
3.b=1;a=1;x=b(1);y=a(1),最终结果是x=1,y=1
我们理解为,线程之间是可以交替执行的,但是线程一内部,也就是:
a=1;
x=b;
这两行代码的执行顺序是不会改变的,也就是a=1会在x=b前执行;同理,线程二的b=1会在y=a前执行。如果以这种思路分析那只能分析出以上三种情况,但其实还有一种容易被我们忽略的情况:x=0,y=0。代码执行结果如下图所示:
出现了x=0,y=0;那是因为发生了重排序现象!其中一种执行可能是:
y = a;
a = 1;
x = b;
b = 1;
线程二中的两行代码先对y赋值,再对b赋值。
1.3 总结
同一个线程中的实际执行顺序和代码在java文件中的顺序不一致,这就是重排序。
二.重排序的好处
重排序可以提高处理速度,比如说下图的例子:
三.重排序的3种情况
3.1 编译器优化
包括JVM、JIT编译器等。 就像上面的情况一样,如果编译器认为可以提高效率那就会进行重排序。
3.2 CPU指令重排序
CPU和编译器这两者之间考虑的角度不同,判断用到的算法也不同。就算编译器不发生重排,CPU也可能对指令进行重排。
3.3 内存的“重排序”
这里是双引号的重排序, 可以看出这里不是真正的重排序。内存中的重排序只是表面现象,看上去是和重排序一样的效果。因为内存中存在缓存的概念,对应JMM中的主内存和工作内存,线程A的修改没有即时刷新到主存中,导致线程B看不到实时更新的值,看上去是重排序执行的效果。