2010-09-01 259 views
9

爲什麼認爲模式被破壞?我看起來很好嗎?有任何想法嗎?雙重鎖定鎖定模式:是否損壞?

public static Singleton getInst() { 
    if (instace == null) createInst(); 
    return instace; 
} 

private static synchronized createInst() { 
    if (instace == null) { 
     instace = new Singleton(); 
    } 
} 
+2

通過使用DI/IOC容器並允許容器控制對象的生命週期,而不是將此類邏輯嵌入對象本身,您可以完全避免此問題....不是一個答案,而是需要思考的問題。 – Stimul8d 2010-09-01 12:28:09

+0

問題在這裏發佈的代碼是否算作雙重檢查鎖定的示例?鎖正在被檢查一次。 – 2010-09-01 13:56:11

+0

請參閱http://stackoverflow.com/questions/3578604/how-to-solve-the-double-checked-locking-is-broken-declaration-in-java/3578674#3578674 – irreputable 2010-09-01 18:14:43

回答

21

乍一看看起來不錯,但這種技術有許多微妙的問題,通常應該避免。例如,考慮事件的順序如下:

  1. 線程A通知該值 沒有初始化,所以它獲得 鎖,並開始初始化 值。
  2. 由編譯器生成的代碼被允許 A已完成 執行初始化之前的共享變量來更新到 指向部分構造 對象。
  3. 線程B注意到共享的 變量已被初始化(或出現 ),並返回其值。 因爲線程B認爲值 已經被初始化,所以它不會獲取鎖定 。如果B使用 對象,則在由A完成的 初始化被 B看到之前,程序可能會崩潰。

你可以通過使用「揮發性」關鍵字來正確處理您的單實例

+2

+1 - Java中雙重檢查鎖定模式問題的唯一答案! – helios 2010-09-01 10:08:39

+1

揮發物仍然帶來一些問題,看看我的答案。 – atamanroman 2010-09-01 10:31:04

+1

揮發性或惰性加載都不是正確的方法,而是使用靜態初始化程序 – 2010-09-01 13:55:04

7

我不知道它是否壞了,但它不是真正的最有效的解決方案,因爲同步這很昂貴。 更好的方法是使用'Initialization On Demand Holder Idiom',它會在你的第一次請求時將你的單例加載到內存中,就像名字所暗示的那樣,這就是懶加載。 這個習語最大的好處是你不需要同步,因爲JLS確保類加載是連續的。關於這個問題

詳細的維基百科條目:http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

另一件事要記住的是,由於依賴注入框架如Spring和吉斯已經出現,正在創建創建和這些容器管理的類實例,他們將如果需要,可以爲你提供一個Singleton,所以除非你想學習模式背後的想法,否則不值得打破它,這很有用。 另請注意,這些IOC容器提供的單例是每個容器實例的單例,但通常每個應用程序都有一個IOC容器,所以它不會成爲問題。

+1

只創建實例同步 – 2010-09-01 10:06:51

+1

好的習慣用法。 (無論如何,問題仍然沒有答案)。我應該補充說,與較新的JVM相比,同步的成本更低。 – helios 2010-09-01 10:13:07

4

Initialization On Demand Holder Idiom避免這種情況,是啊,就是這樣:

public final class SingletonBean{ 

    public static SingletonBean getInstance(){ 
     return InstanceHolder.INSTANCE; 
    } 

    private SingletonBean(){} 

    private static final class InstanceHolder{ 
     public static final SingletonBean INSTANCE = new SingletonBean(); 
    } 

} 

雖然約書亞布洛赫還建議在枚舉單件模式Effective Java第2章第3項:

// Enum singleton - the prefered approach 
public enum Elvis{ 
    INSTANCE; 
    public void leaveTheBuilding(){ ... } 
} 
6

問題是fo下降:您的JVM可能會重新排列您的代碼,並且字段對於不同的線程並不總是相同的。看看這個:http://www.ibm.com/developerworks/java/library/j-dcl.html。使用volatile關鍵字應該可以解決這個問題,但是它在java 1.5之前破壞了。

大部分時間單檢鎖是不是足夠快的多,試試這個:

// single checked locking: working implementation, but slower because it syncs all the time 
public static synchronized Singleton getInst() { 
    if (instance == null) 
     instance = new Singleton(); 
    return instance; 
} 

也看看有效的Java,在那裏你會發現關於這個話題的偉大篇章。

總結一下:不要做雙重檢查鎖定,有更好的idoms。

11

整個討論是一個巨大的,無盡的大腦浪費時間。 99.9%的時間,單身人士沒有任何顯着的設置成本,並且沒有任何理由設計實現非同步保證延遲加載。

這是你如何寫一個Singleton在Java中:

public class Singleton{ 
    private Singleton instance = new Singleton(); 
    private Singleton(){ ... } 
    public Singleton getInstance(){ return instance; } 
} 

更重要的是,讓一個枚舉:

public enum Singleton{ 
    INSTANCE; 
    private Singleton(){ ... } 
} 
+0

您可能還想使該類最終(儘管私有構造函數朝着該方向) – 2010-09-01 10:54:45

+1

+1,雙重檢查鎖定是所有過早納米優化的母體 – flybywire 2010-09-02 12:17:15

+0

對於枚舉構造函數,private是多餘的。編譯器將使其保密。它在規範中寫下來:「在枚舉聲明中,沒有訪問修飾符的構造函數聲明是私有的。」請參見https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.2 – user674669 2017-04-16 07:25:53

2

這不回答你的問題(別人已經做過),但我想告訴你我使用單例/懶惰初始化對象的經驗:

我們在代碼中有幾個單例。一旦我們必須向一個單例添加一個構造函數參數並且存在嚴重問題,因爲此單例的構造函數是在getter上調用的。當時只以下可能的解決方案:

  • 提供一種用於需要初始化該單的對象的靜態吸氣劑(或另一單體)
  • 傳遞對象初始化單作爲參數用於吸氣或
  • 通過傳遞實例來擺脫單身人士。

最後,最後的選擇是要走的路。現在我們在應用程序啓動時初始化所有對象並傳遞所需的實例(可能是一個小接口)。我們沒有後悔這個決定,因爲

  • 一段代碼的依賴是非常明確的,
  • 我們可以測試我們的代碼更通過提供所需的對象的虛擬實現方式更容易。
+3

Singleton模式確實被過度使用,並且通常是設計不佳的標誌 – 2010-09-01 13:49:14

2

這裏的大多數答案都是正確的,說明它爲什麼會被破壞,但是不正確或者提出可疑的解決方案。

如果你真的,真的必須使用單(在大多數情況下,你不應該,因爲它破壞的可測性,結合了有關如何構建一個類與類的行爲邏輯,亂丟使用的類單身人士知道如何獲得一個,並導致更脆弱的代碼),並擔心同步,正確的解決方案是使用a static initializer實例化實例。

private static Singleton instance = createInst(); 

public static Singleton getInst() { 
    return instance ; 
} 

private static synchronized createInst() { 
    return new Singleton(); 
} 

Java語言規範保證了靜態initiailzers將只運行一次,當一個類加載的第一次,並在保證線程安全的方式。