天天看點

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 讀之前。

繼續閱讀