volatile 關鍵字

Adrian_Dai發表於2018-05-03

 轉載自:https://javadoop.com/post/java-memory-model#toc10


volatile 的作用,記住兩點:記憶體可見性和禁止指令重排序。


volatile 的記憶體可見性

我們還是用 JMM 的主記憶體和本地記憶體抽象來描述,這樣比較準確。還有,並不是只有 Java 語言才有 volatile 關鍵字,所以後面的描述一定要建立在 Java 跨平臺以後抽象出了記憶體模型的這個大環境下。

 

還記得 synchronized 的語義嗎?進入 synchronized 時,使得本地快取失效,synchronized 塊中對共享變數的讀取必須從主記憶體讀取;退出 synchronized 時,會將進入 synchronized 塊之前和 synchronized 塊中的寫操作刷入到主存中。

 

volatile 有類似的語義,讀一個 volatile 變數之前,需要先使相應的本地快取失效,這樣就必須到主記憶體讀取最新值,寫一個 volatile 屬性會立即刷入到主記憶體。所以,volatile 讀和 monitorenter 有相同的語義,volatile 寫和 monitorexit 有相同的語義。

 

volatile 的禁止重排序

大家看下面的雙重檢查的單例模式,加個 volatile 能解決問題。其實就是利用了 volatile 的禁止重排序功能。

public static Singleton getInstance() {
    if (instance == null) { //
        Singleton temp;
        synchronized (Singleton.class) { //
            temp = instance;
            if (temp == null) { //
                synchronized (Singleton.class) { // 內嵌一個 synchronized 塊
                    temp = new Singleton();
                }
                instance = temp; //
            }
        }
    }
    return instance;
}

synchronized 在退出的時候,能保證 synchronized 塊中對於共享變數的寫入一定會刷入到主記憶體中。也就是說,上述程式碼中,內嵌的 synchronized 結束的時候,temp 一定是完整構造出來的,然後再賦給 instance 的值一定是好的。

可是,synchronized 保證了釋放監視器鎖之前的程式碼一定會在釋放鎖之前被執行(如 temp 的初始化一定會在釋放鎖之前執行完 ),但是沒有任何規則規定了,釋放鎖之後的程式碼不可以在釋放鎖之前先執行。

也就是說,程式碼中釋放鎖之後的行為 instance = temp 完全可以被提前到前面的 synchronized 程式碼塊中執行,那麼重排序問題就又出現了。



volatile 的禁止重排序並不侷限於兩個 volatile 的屬性操作不能重排序,而且是 volatile 屬性操作和它周圍的普通屬性的操作也不能重排序。

之前 instance = new Singleton() 中,如果 instance volatile 的,那麼對於 instance 的賦值操作(賦一個引用給 instance 變數)就不會和建構函式中的屬性賦值發生重排序,能保證構造方法結束後,才將此物件引用賦值給 instance

根據 volatile 的記憶體可見性和禁止重排序,那麼我們不難得出一個推論:執行緒 a 如果寫入一個 volatile 變數,此時執行緒 b 再讀取這個變數,那麼此時對於執行緒 a 可見的所有屬性對於執行緒 b 都是可見的。

 

volatile 小結

1.volatile 修飾符適用於以下場景:某個屬性被多個執行緒共享,其中有一個執行緒修改了此屬性,其他執行緒可以立即得到修改後的值。在併發包的原始碼中,它使用得非常多。

2.volatile 屬性的讀寫操作都是無鎖的,它不能替代 synchronized,因為它沒有提供原子性和互斥性。因為無鎖,不需要花費時間在獲取鎖和釋放鎖上,所以說它是低成本的。

3.volatile 只能作用於屬性,我們用 volatile 修飾屬性,這樣 compilers 就不會對這個屬性做指令重排序。

4.volatile 提供了可見性,任何一個執行緒對其的修改將立馬對其他執行緒可見。volatile 屬性不會被執行緒快取,始終從主存中讀取。

5.volatile 提供了 happens-before 保證,對 volatile 變數 v 的寫入 happens-before 所有其他執行緒後續對 v 的讀操作。

6.volatile 可以使得 long double 的賦值是原子的。


相關文章