Java内存模型之重排序

文章目录

  • 1.什么是重排序
  • 2.重排序的好处
  • 3.重排序的三种情况
  • 4.用volatile修正重排序问题

1.什么是重排序

首先来看一个代码案例,尝试分析一下 x 和 y 的运行结果。

import java.util.concurrent.CountDownLatch;

/**
 * 演示重排序的现象,直到达到某个条件才停止,测试小概率事件
 */
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;

        while (true) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            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 = " + x + ", y = " + y + ")";
            if (x == 0 && y == 0) {
                System.out.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

经过分析可知,线程 one 的第 33 行和第 34 行和线程 two 的第 47 行和第 48 行是核心代码,这 4 行代码的执行顺序决定了最终 x 和 y 的结果。

根据大多数人对多线程的认知,在线程 one 内部,第 33 行和第 34 行代码的执行顺序是不会改变的,即 a=1 会在 x=b 前执行;在线程 two 内部,第 47 行和第 48 行代码的执行顺序是不会改变的,即 b=1 会在 y=a 前执行。在此前提下,x 和 y 的最终结果一共有 3 种情况:

  • 最终结果是 x=0, y=1,可能的执行顺序是 a=1; x=b; b=1; y=a;
  • 最终结果是 x=1, y=0,可能的执行顺序是 b=1; y=a; a=1; x=b;
  • 最终结果是 x=1, y=1,可能的执行顺序是 b=1; a=1; x=b; y=a;

然而,在实际运行过程中,会出现 x=0, y=0 的结果,这是因为重排序发生了,其中一种可能的代码执行顺序是 y=a; a=1; x=b; b=1;

Java内存模型之重排序_第1张图片

至此,便可解答一个问题:什么是重排序?

在线程内部的两行代码的实际执行顺序和代码在Java文件中的顺序不一致,代码指令并不是严格按照代码语句顺序执行的,它们的顺序被改变了,这就是重排序。

2.重排序的好处

对比下面重排序前后的指令优化,我们可以发现,重排序的好处是可以提高处理速度。

Java内存模型之重排序_第2张图片

Java内存模型之重排序_第3张图片

3.重排序的三种情况

  • 编译器优化:包括JVM,JIT编译器等。
  • CPU指令重排:就算编译器不发生重排,CPU也可能对指令进行重排。
  • 内存的“重排序”:线程 A 的修改线程 B 却看不到,引出可见性问题。

4.用volatile修正重排序问题

使用 volatile 关键字修正上面的 OutOfOrderExecution 类,加了 volatile 后,就不会出现 (x=0, y=0) 的情况了。

import java.util.concurrent.CountDownLatch;

/**
 * 使用volatile关键字修正重排序问题
 */
public class OutOfOrderExecution {

    private volatile static int x = 0, y = 0;
    private volatile static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;

        while (true) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            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 = " + x + ", y = " + y + ")";
            if (x == 0 && y == 0) {
                System.out.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

你可能感兴趣的:(Java,java,jmm,重排序)