2013-04-03 52 views
19

我正在做一些關於單例的研究,特別是關於單例的懶惰和渴望的初始化。延遲加載和線程安全相結合的單例模式

渴望初始化的例子:

public class Singleton 
{ 
    //initialzed during class loading 
    private static final Singleton INSTANCE = new Singleton(); 

    //to prevent creating another instance of Singleton 
    private Singleton(){} 

    public static Singleton getSingleton(){ 
     return INSTANCE; 
    } 
} 

但高於它是渴望初始化和線程安全留給JVM 但現在,我希望能有同樣的模式,但與延遲初始化,如圖所示。

所以我想出這種方法:

public final class Foo { 
    private static class FooLoader { 
     private static final Foo INSTANCE = new Foo(); 
    } 
    private Foo() { 
     if (FooLoader.INSTANCE != null) { 
      throw new IllegalStateException("Already instantiated"); 
     } 
    } 
    public static Foo getInstance() { 
     return FooLoader.INSTANCE; 
    } 
} 

如上所示由於線

private static final Foo INSTANCE = new Foo(); 

當類FooLoader實際使用時才執行,這需要照顧的延遲實例的,並且它保證是線程安全的。

這是正確的嗎?

+0

Duplicate http://stackoverflow.com/q/5842273/823393 – OldCurmudgeon

+1

這不是真的懶惰的初始化嗎?該實例可以在調用getInstance方法之前加載。 – Perception

+0

這當然是懶惰的。 「INSTANCE」僅在該類的第一個引用中實例化。這隻會在第一次調用'getInstance'時引用。雖然我會刪除構造函數中的創建檢查。 –

回答

17

在我看來,你的第二個代碼片段是線程安全懶惰初始化單例的最好方法。它實際上有一個模式名稱

Initialization-on-demand holder idiom

我會建議你使用它。

+0

非常感謝。通過文章完美探索 –

+0

回覆JVM的類加載行爲,而不是讓你的懶惰明確的聲音對我來說就像一個壞主意。與直接的方式相比,甚至可能沒有性能優勢。 – einpoklum

+0

@einpoklum我看到你的例子使用了DCL + volatile。雖然它是正確的(我們將其聲明爲不穩定),但我寧願使用他的後一個例子,然後是DCL。我發現它更具可讀性,而且不那麼冗長。最後你的例子是錯誤的,你應該在'Foo.class'上同步而不是實例。 –

1

最好的辦法實際上是使用Enum Way

public enum Singleton { 
    INSTANCE; 
    public void execute (String arg) { 
      //... perform operation here ... 
    } 
} 
+1

我會說這是最糟糕的方式......而布洛赫應該退還他的讀者:) – ZhongYu

+0

嘿,我知道:這傢伙很糟糕...... –

+0

+1 zhong.j.yu即使布洛赫談論過,我也從來不喜歡這種模式它如此之高,然後其他人都跟着它:/ –

2

第二個是在可讀性方面非常糟糕,第一個是合適的。看看這個article。它關於雙重檢查鎖定,但也會給你關於單身多線程的廣泛信息。

+0

第二個如何*非常糟糕*? –

+0

它會工作,但我沒有看到FooLoader中的任何用法以及構造函數中的if語句。讓它不壞但負擔過重。記住其他人會閱讀你的代碼。所以保持簡單。 – Mikhail

+0

我同意,在構造函數中檢查是沒有必要的,應該刪除。但是當談論線程安全性時,我認爲第二個代碼片段絕對沒有錯。 –

5

你的第一個設計實際上是懶惰的。想想看,這個實例只是在類被初始化時才創建的;該類只在調用getSingleton()方法時初始化[1]。所以這個實例只有在被要求的時候纔會被創建,也就是說它被延遲創建。

[1] http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.1

+0

+1你是對的它仍然懶惰,雖然我想他會有其他方法可用,其中一個不同的靜態引用會創建它。 –

+0

儘管singleton類不太可能包含靜態方法(除了getInstance()') – ZhongYu

+2

,但它不是不可能包含公共靜態字段:) –

0

在我看來,這是使用不恰當的模式。它對JVM的行爲進行了假設,這些行爲是不平凡的和令人困惑的。另外,它有一個虛擬類。儘可能避免假類。

我建議直接的方法:

public class Foo { 
    private volatile static final Foo instance = null; 

    private Foo() { 
    } 

    public static Foo instance() { 
     if (instance == null) instance = new Foo(); 
     return instance; 
     } 
    } 
} 

...雖然,這是行不通的 - 是 - 它不是線程安全的..你真正想要的是在第71條提出的雙重檢查圖案Bloch's Effective Java;見here。修改實例在鏈接到你的情況,我們得到:

public class Foo { 
    private volatile static final Foo instance = null; 

    private Foo() { 
    } 

    public static Foo instance() { 
     if (instance != null) return instance; 
     synchronized(instance) { 
      Foo result = instance; 
      if (instance == null) { 
       result = instance = new Foo(); 
      return result; 
     } 
    } 
} 

注:

  • 不要擔心這個代碼的性能,現代JVM照顧它,它就好了。畢竟,premature optimization is the root of all evil
  • 正如其他答案中的建議,上述不是布洛赫的首選解決方案,但我認爲使用一個單例的枚舉在語義上是不恰當的,就像OP最初所做的一樣。
+1

如果您不擔心性能,只需使用第一個示例並同步整個方法。更簡單! –

+1

有關雙鎖的其他版本,請參閱https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java –