Java使用double check(雙重檢查)實現單例模式的一個小細節
阿新 • • 發佈:2019-02-06
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修飾