1. 程式人生 > >Java使用double check(雙重檢查)實現單例模式的一個小細節

Java使用double check(雙重檢查)實現單例模式的一個小細節

public class Singleton {
    private static Singleton singleton;
    private Singleton() {

    }
    public static Singleton getInstance() {
        if (singleton == null) {//1
            synchronized {//2
                if (singleton == null) {//3
                    singleton = new Singleton();//4             
} } } } }

這樣實現的單例其實是不安全的,執行語句4時,實際包含3個步驟:
a. 給singleton分配記憶體
b. 在記憶體中初始化singleton物件
c. 將記憶體地址賦給singleton變數(這時singleton變數就不為null了)

因為編譯器會進行指令重排,如果指令重排之後第c步先於第b步執行,那可能會發生如下的錯誤:
1)執行緒1執行語句4,這時執行緒1工作記憶體的singleton變數不為null,可能會立即寫回到主存中,也可能遲點再寫回到主存中。

2)然後這時執行緒的時間分片又剛好用完了,就會切換到執行緒2,如果執行緒1的singleton已經寫回到主存中,那麼這時執行緒2執行語句1就為false,然後返回single物件,但實際上第2步還沒執行,即物件還沒初始化,使用該物件會導致程式報錯。

解決的方法:
使用volatile修飾singleton變數,volatile保證對volatile變數進行讀/寫操作的那一行程式碼的順序不變,即第c步順序就一定是在b之後的,確保物件初始化完再將記憶體地址賦給singleton變數。第c步就是對volatile變數進行寫操作。

synchronized雖然能保證互斥,但是不保證在一個時間分片內將程式碼塊中的所有程式碼執行完畢,會釋放時間分片,等待再分配時間分片再繼續執行下去。

結論:Java使用double check(雙重檢查)實現單例模式時,單例變數要使用volatile修飾