Java内存模型之重排序

435 阅读3分钟

一.什么是重排序

我们先不用管概念,先看现象,后面会总结,用自顶向下的思维去理解概念。直接看代码演示现象。

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看不到实时更新的值,看上去是重排序执行的效果。