天天看點

java中volatile關鍵字

  jmm提供了volatile變量定義、final、synchronized塊來保證可見性。

  用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改後的最的值。volatile很容易被誤用,用來進行原子性操作。寫了幾個測試的例子,大家可以試一試。

java中volatile關鍵字
java中volatile關鍵字

  1、沒有volatile,沒有synchronized的情況   

java中volatile關鍵字
java中volatile關鍵字

    輸出結果:8, 9, 10都出現過。可以多運作,多試一試,就會發現不同的結果。

  2、有volatile,沒有synchronized

java中volatile關鍵字
java中volatile關鍵字

    輸出結果:最多出現的是9 和 10。

  3、沒有volatile,有synchronized

java中volatile關鍵字
java中volatile關鍵字

  輸出結果:無論運作多少次都是10。

java中volatile關鍵字
java中volatile關鍵字

  首先說明一下,為什麼這種寫法在java中是行不通的!

  假設線程Ⅰ是初次調用getinstance()方法,緊接着線程Ⅱ也調用了getinstance()方法和getsomefield()方法,我們要說明的是線程Ⅰ的語句(1)并不happen-before線程Ⅱ的語句(7)。線程Ⅱ在執行getinstance()方法的語句(2)時,由于對instance的通路并沒有處于同步塊中,是以線程Ⅱ可能觀察到也可能觀察不到線程Ⅰ在語句(5)時對instance的寫入,也就是說instance的值可能為空也可能為非空。我們先假設instance的值非空,也就觀察到了線程Ⅰ對instance的寫入,這時線程Ⅱ就會執行語句(6)直接傳回這個instance的值,然後對這個instance調用getsomefield()方法,該方法也是在沒有任何同步情況被調用,是以整個線程Ⅱ的操作都是在沒有同步的情況下調用 ,這說明線程Ⅰ的語句(1)和線程Ⅱ的語句(7)之間并不存在happen-before關系,這就意味着線程Ⅱ在執行語句(7)完全有可能觀測不到線程Ⅰ在語句(1)處對somefiled寫入的值,這就是dcl的問題所在。很荒謬,是吧?dcl原本是為了逃避同步,它達到了這個目的,也正是因為如此,它最終受到懲罰,這樣的程式存在嚴重的bug,雖然這種bug被發現的機率絕對比中彩票的機率還要低得多,而且是轉瞬即逝,更可怕的是,即使發生了你也不會想到是dcl所引起的。

  我的了解是:線程i 和線程ii 都有自己的工作存儲,線程i 建立好了instance後,向記憶體重新整理的時間是不确定的,是以線程Ⅱ在執行語句(7)完全有可能觀測不到線程Ⅰ在語句(1)處對somefiled寫入的值。

  那麼由于在java 5中多增加了一條happen-before規則:

對volatile字段的寫操作happen-before後續的對同一個字段的讀操作。

  利用這條規則我們可以将instance聲明為volatile,即: private volatile static lazysingleton instance; 

    根據這條規則,我們可以得到,線程Ⅰ的語句(5) -> 語線程Ⅱ的句(2) (也就是線程),根據單線程規則,線程Ⅰ的語句(1) -> 線程Ⅰ的語句(5)和語線程Ⅱ的句(2) -> 語線程Ⅱ的句(7),再根據傳遞規則就有線程Ⅰ的語句(1) -> 語線程Ⅱ的句(7),這表示線程Ⅱ能夠觀察到線程Ⅰ在語句(1)時對somefiled的寫入值,程式能夠得到正确的行為。

  補充:在java5之前對final字段的同步語義和其它變量沒有什麼差別,在java5中,final變量一旦在構造函數中設定完成(前提是在構造函數中沒有洩露this引用),其它線程必定會看到在構造函數中設定的值。而dcl的問題正好在于看到對象的成員變量的預設值,是以我們可以将lazysingleton的somefield變量設定成final,這樣在java5中就能夠正确運作了。

  就先寫到這裡了,如果大神路過,可以給我提供一個測試 volatile 的例子,我實在是寫不出來。