Java併發學習 4 :volatile的應用

在Java併發編程中,synchronized和volatile 都扮演着重要的角色,volatile是輕量級的synchronzied,其在多處理器開發時保證了共享變量的"可見性".

問題引入:多個CPU的不可見性造成髒讀

我們知道CPU速度非常快,比內存快百倍以上,所以CPU更希望和速度相近的CPU cache打交道。

而一個多核的CPU本質上就是多個CPU共用一個外殼,每個核就是一個單核CPU,其都有屬於自己的cache。

當更改一個變量時,CPU將值寫入到對應的cache中,不一定寫入內存中,這個CPU認爲值已經改變了,但是其他CPU認爲值沒有改變。這樣就會出現髒讀。

解決方式:volatile 指令實現可見性

volatile 底層是使用Lock指令。

CPU讀取到lock指令後,會做以下操作。

1. 將對應的值先到CPU對應的cache中

2. 將對應的值寫入到內存中(就是內存條,我們稱其爲系統內存)

3. 第二部的寫操作會使在其他CPU裏緩存了該地址的數據無效。

4. 其他CPU當需要這個數值時,必須去重新讀取內存。

這樣就保證了多CPU之間的可見性。

經典案例:基於雙重檢查的單例模式

單例模式就是保證一個類始終只有一個對象的一種編碼方法。我們可以很容易的寫出來一個單線程下的單例餓漢模式

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            // 可能同時有多個線程進入if中
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

在多線程時,可以會new出多個實例.

我們可以通過synchronized關鍵子鎖住這個方法,保證其線程安全。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

synchronized是把這個方法鎖成一個單線程方法,效率降低十分明顯。

我們通過volatile進行優化,將其synchronized鎖的粒度減小。

public class Singleton { 
    // 必須使用 volatile 修飾,防止因爲多CPU之間的不可見性而出錯。
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章