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