天天看点

Java 指令重排序

Java 指令重排序

Java 内存模型运行编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。在单线程下可以保证最终的结果与程序顺序执行的结果一致,但是在多线程下就会存在问题。

例如:

int a = 1;(1)
int b = 2;(2)
int c = a + b;(3)
           

在上面代码中,变量 c 的值依赖 a 和 b 的值,所以重排序后能够保证 (3) 的操作在 (2)和(1)之后,但是(1)(2)谁先执行就不一定了,这在单线程下不会存在问题,因为并不影响最终的结果。

Public class Test {

    private static int num = 0;
    private static boolean ready = false;

    public static class ReadThread extends Thread {

        @Override
        public void run() {

            while (!Thread.currentThread().isInterrupted()) {
                if (ready) {//(1)
                    System.out.println(num + num);//(2)
                }
                System.out.println("read thread ...");
            }
        }
    }

    public static class WriteRead extends Thread {

        @Override
        public void run() {
            num = 2;//(3)
            ready = true;//(4)
            System.out.println("WriteRead set over");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReadThread rt = new ReadThread();
        rt.start();

        WriteRead wr = new WriteRead();
        wr.start();

        Thread.sleep(10);
        rt.interrupt();
        System.out.println("main exit");
    }
}
           

这段代码里面的变量没有被声明为 volatile的,也没有使用任何同步措施,所以在多线程下存在共享变量内存可见性问题。这里先不谈可见性问题,因为通过把变量声明为 volatile 的本身就可以避免指令重排序问题。

上面代码在不考虑内存可见性问题的情况下不一定会输出4。由于代码(1)(2)(3)(4)之间不存在依赖关系,所以写线程的代码(3)(4)可能被重排序为先执行(4)再执行(3),那么执行(4)后,读线程可能已经执行了(1)操作,并且在(3)执行前开始执行(2)操作,这时候输出结果为0而不是4。

重排序在多线程下会导致非预期的程序执行结果,而使用 volatile 修饰 ready 就可以避免重排序和内存可见性问题。

写 volatile 变量时,可以确保 volatile 写之前的操作不会被编译器重排序到 volatile 写之后。读 vola 变量时,可以确保 volatile 读之后的操作不会被编译器重排序到 volatile 读之前。

继续阅读