[英]Double checked locking without using volatile-keyword and without synchronizing the entire getInstance() method
以下是我的單例類,我使用雙重檢查鎖定而不使用volatile關鍵字而不同步整個getInstance()方法:
public class MySingleton {
private static MySingleton mySingleton;
public static MySingleton getInstance() {
if(mySingleton == null) {
synchronized(MySingleton.class) {
if(mySingleton == null) {
MySingleton temp = new MySingleton();
mySingleton = temp;
}
}
}
return mySingleton;
}
}
據我說,這是線程安全的。 如果有人認為,這不是線程安全的,有人可以詳細說明為什么他/她認為這不是線程安全的嗎? 謝謝。
這里我們有一個synchronized
寫入和一個non-synchronized
讀取。 我們無法保證讀取將獲得寫入設置的值。 所以這段代碼很麻煩。 但這是一個非常微妙的錯誤,特別是在多核CPU中。
如果由於某種原因需要繼續使用上面的代碼,一種解決方案是將變量聲明為volatile
如下所示:
private static volatile MySingleton mySingleton;
或者使變量的讀取synchronized
。 它們中的任何一個都會以性能為代價來解決您的問題。
另一個重點是你的temp
變量在這里沒用。 它只是一個冗余的代碼,什么都不做。 而是直接將它分配給你的mySingleton
變量,如下所示,
mySingleton = new MySingleton();
通過使用enum
類型來制作單例,可以非常容易地解決所有上述問題。 由於枚舉本質上是Serializable
,因此我們不需要使用Serializable
接口來實現它。 反思問題也不存在。 因此,100%保證在JVM中只存在一個Singleton
實例。 因此,建議將此方法作為在Java中制作單例的最佳方法。
在我閱讀所有評論之前,我不知道這個問題。 問題是各種優化過程(編譯器,熱點,無論如何)都會重寫代碼。 您的“臨時”解決方案很容易被刪除。 我發現很難相信構造函數可以返回一個部分對象,但如果知識淵博的貢獻者這樣說,我會相信他們的意見。
是的,但我使用的是“臨時”變量。 它不解決“部分創建對象”問題嗎?
不,不是的。
假設一些線程A調用getInstance()
,最后創建一個新實例並分配mySingleton
變量。 然后線程T出現,調用getInstance()
並看到mySingleton
不為null
。
此時,線程T
沒有使用任何同步。 如果沒有同步,Java語言規范(JLS)不要求線程T以與線程A相同的順序查看線程A所做的分配。
我們假設單例對象有一些成員變量。 線程A顯然必須在將引用存儲到mySingleton
之前初始化這些變量。 但是JLS允許線程T看到mySingleton != null
但仍然看到成員變量處於未初始化狀態。 在一些多核平台上,它實際上可以這樣發生。
首先將對象引用分配給本地temp
變量不會改變任何內容。 實際上,正如Steve11235所指出的那樣, temp
變量甚至可能實際上不存在於字節代碼或本機指令中,因為Java編譯器或熱點編譯器可以完全優化它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.