奇怪的問題
以下的程式碼,我在工作中也是這樣寫,邏輯上似乎沒有問題才對,沒想到讀了這段文章之後,才知道我可能寫錯了!!!public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { while (!ready) Thread.yield(); System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } }
錯的原因在於ReaderThread不見得看得到ready和number這兩個變數的改變!為什麼會這樣?
ReaderThread可能發生情況包括:(1) 無窮迴圈 (2) 印出number為0 (3)按照我們的期望執行印出42
(1)(2)發生的原因難以理解,因為這是我們programmer看不到的邏輯:compiler optimization造成的execution reordering,因為對compiler來說某些指令如果沒有dependency的話,先後順序可以不按照程式行碼的順序來編排,這是為了多核心CPU所做的最佳化。
所以在main method中,創造新的ReaderThread, 寫入number = 42 和 寫入 ready = true這三行順序可能被compiler重新安排順序,因為兩者沒有關聯性,所以我們依照這個順序性成立的假設來實作ReaderThread的run method是錯誤的假設!
不過不理解為什麼會無窮迴圈?因為ready最終會被設為true,好奇怪?按照書中說法,當讀寫shared variable時候,其他thread不保證會看到ready = true被寫入這件事,書中提到可能是因為CPU cache造成ReaderThread讀到outdated ready value,這倒是說得通,不過這樣的optimization也未免太嚇人,multithreading的時候要注意optimization造成的side effect。
我個人在桌機執行上面的程式,儘管開了5條ReaderThread也是印出42,所以可能跟硬體和JVM compiler的設定有關。
不求甚解的我,conclude書中的話好了:反正有被multithreads shared的variables,都請用synchronization保護!
Stale data
上面例子說到ReaderThread可能看到非預期的變數值,因為compiler optimization造成的,這種過期(有時候混合正確的值)的資料稱為stale data,非常難以用邏輯理解(因為我們的邏輯需要建設在某些假設上,但這些假設在compiler optimization之後就不成立)。重點在“讀取”shared variables也一定要synchronized保護,否則stale data是可能發生的。只有這樣能保證不同thread讀到最新的值。
一個可能發生的怪現象是JVM允許implementation把64 bit non-volatile long/double 用兩個32 bit read/write指令來完成指令來完成 原本應該是atomic operation的fetch and store operation,所以也有可能發生upper 或 lower 32 bits是stale value,相當恐怖,這一般應該查不出原因,如果不知道這麼眉角的話。
Java提供了volatile keyword,告訴compiler這個variable是shared,不要做reordering,可以解決這個問題,但是並沒有synchronization的保護。
沒有留言:
張貼留言