2011-05-13 117 views
8

這不是一個簡單而且安全(因此更好)的方式來實現一個單例,而不是做雙重檢查的鎖定曼波 - 曼波嗎?這種方法的任何缺點?Simpleton的簡單實現


public class Singleton 
{ 
    private static Singleton _instance; 
    private Singleton() { Console.WriteLine("Instance created"); } 

    public static Singleton Instance 
    { 
     get 
     { 
      if (_instance == null) 
      { 
       Interlocked.CompareExchange(ref _instance, new Singleton(), null); 
      } 
      return _instance; 
     } 
    } 
    public void DoStuff() { } 
} 

編輯:測試線程安全的失敗,任何人都可以解釋,爲什麼? Interlocked.CompareExchange怎麼不是真正的原子?


public class Program 
{ 
    static void Main(string[] args) 
    { 
     Parallel.For(0, 1000000, delegate(int i) { Singleton.Instance.DoStuff(); }); 
    } 
} 

Result (4 cores, 4 logical processors) 
Instance created 
Instance created 
Instance created 
Instance created 
Instance created 
+3

難道這個例子仍然會造成一個問題,如果兩個線程都能夠得到'如果(_instance == NULL)內'檢查他們之一執行'Interlocked.CompareExchange'功能? – Tim 2011-05-13 19:23:26

+1

這是不正確的。根據MSDN文檔:「比較和交換操作是作爲原子操作執行的。」 – kateroh 2011-05-13 19:33:48

+0

@ kateroh - 啊,我明白了,我在同一時間將它作爲一個比較和交換。所以它是一個雙重檢查。我的錯。 – Tim 2011-05-13 19:39:41

回答

8

您可能要創建多個實例,但這些會得到垃圾回收,因爲他們沒有任何地方使用。在任何情況下,static _instance字段變量都不會多次更改其值,即它從null變爲有效值的單個時間。因此,儘管已創建多個實例,但此代碼的使用者只會看到相同的實例。

鎖定自由編程

Joe Duffy,在其題爲在Windows併發編程的書其實這種分析非常模式,你嘗試在第10章,內存模式和鎖自由使用,526頁

他指的是這種模式作爲參考寬鬆的延遲初始化:

public class LazyInitRelaxedRef<T> where T : class 
{ 
    private volatile T m_value; 
    private Func<T> m_factory; 

    public LazyInitRelaxedRef(Func<T> factory) { m_factory = factory; } 


    public T Value 
    { 
     get 
     { 
      if (m_value == null) 
       Interlocked.CompareExchange(ref m_value, m_factory(), null); 
      return m_value; 
     } 
    } 

    /// <summary> 
    /// An alternative version of the above Value accessor that disposes 
    /// of garbage if it loses the race to publish a new value. (Page 527.) 
    /// </summary> 
    public T ValueWithDisposalOfGarbage 
    { 
     get 
     { 
      if (m_value == null) 
      { 
       T obj = m_factory(); 
       if (Interlocked.CompareExchange(ref m_value, obj, null) != null && obj is IDisposable) 
        ((IDisposable)obj).Dispose(); 
      } 
      return m_value; 
     } 
    } 
} 

正如我們所看到的,在上面的示例方法中,以創建丟棄對象的代價是免費的。在任何情況下,對於這樣的API的消費者,Value屬性都不會改變。

平衡取捨

鎖定自由是有代價的,是仔細選擇你的取捨的問題。在這種情況下,鎖定自由的代價是你必須創建你不打算使用的對象的實例。這可能是一個可以接受的價格,因爲你知道,通過鎖定免費,死鎖的風險較低,並且還存在線程爭用。

在這種特別實例然而,一個單身的語義在本質上創建一個對象的實例,所以我寧願選擇Lazy<T>作爲@Centro在他的回答引用了。

儘管如此,它仍然存在問題,當應該我們使用Interlocked.CompareExchange?我喜歡你的榜樣,這是相當激動人心的,很多人很快就會錯誤地將它當作錯誤,當它不是可怕的錯誤的@Bindy引號。

這一切都歸結到你是否計算了權衡和決定:

  • 有多重要,你產生一個且只有一個實例?
  • 鎖定自由有多重要?

只要你意識到權衡,並且有意識地決定創建新的對象以獲得無鎖的好處,那麼你的例子也可以是可接受的答案。

3
public class Singleton 
{ 
    private static Singleton _instance = new Singleton(); 
    private Singleton() {} 

    public static Singleton Instance 
    { 
     get 
     { 
      return _instance; 
     } 
    } 
} 
+0

偉大的思想和所有:) – Blindy 2011-05-13 19:24:14

+0

哪裏是靜態構造函數? – ZOXEXIVO 2016-08-09 23:04:20

+0

@ZOX - 不需要。 – Oded 2016-08-10 08:25:41

9

如果單是曾經在多次初始化自身的危險,你有很多更嚴重的問題。爲什麼不使用:

public class Singleton 
{ 
    private static Singleton instance=new Singleton(); 
    private Singleton() {} 

    public static Singleton Instance{get{return instance;}} 
} 

關於初始化,絕對是線程安全的。

編輯:如果我不清楚,你的代碼是可怕的錯誤if檢查和new都是不是線程安全!你需要使用適當的單例類。

+1

這並沒有給出與海報代碼相同的功能。 OP的代碼只在被引用時才返回_instance(所以如果使用,只會初始化它),而無論類是否需要,您的代碼都會初始化它。 – Streklin 2011-05-13 19:26:27

+0

@Streklin,我不會對這位先生賭得太多...... – Blindy 2011-05-13 19:27:19

+0

嗯夠公平的 - 我不會這麼做的。但你介意解釋我錯過了什麼?我沒有看到OP的代碼如何被初始化,除非在Instance屬性上調用get函數。 – Streklin 2011-05-13 19:30:03

3

我不相信你完全可以相信這一點。是的,Interlocked.CompareExchanger是原子的,但新的Singleton()在任何非平凡的情況下都不會是原子的。由於在交換值之前必須進行評估,因此這通常不是線程安全的實現。

+0

更不用說在調用互鎖函數之前進行非原子比較... – Blindy 2011-05-13 19:24:49

1

這不是線程安全的。

您需要一個鎖來將if()Interlocked.CompareExchange()放在一起,然後您就不再需要CompareExchange了。

1

你仍然有問題,你很可能創建和扔掉你的單身人士的實例。執行Interlocked.CompareExchange()時,無論賦值是否成功,構造函數總是會被執行。所以,你沒有更好(或者更糟,恕我直言)比,如果你說:

if (_instance == null) 
{ 
    lock(latch) 
    { 
    _instance = new Singleton() ; 
    } 
} 

更好的性能,面對面的人線程爭比,如果你換了lock的位置和測試空,但面臨着構建額外實例的風險。

+0

在編輯中看到測試,只有在_instance == null時才創建實例 – kateroh 2011-05-13 20:02:58

+1

多於1個線程可以通過null之前的測試'_instance'被設置。比方說,你可以有4個線程堆棧在比較和交換中。一個會成功,但你的單身人士的4個實例將被創建,並且3個將最終被拋棄。而且......你無法控制哪個**線程會實例化單例。在上下文交換方面,先入先出不保證。歡迎來到模糊多線程/多處理世界。 – 2011-05-13 20:19:09

+0

沒錯。 +1 – kateroh 2011-05-13 20:27:02

3

這個怎麼樣?

public sealed class Singleton 
{ 
    Singleton() 
    { 
    } 

    public static Singleton Instance 
    { 
     get 
     { 
      return Nested.instance; 
     } 
    } 

    class Nested 
    { 
     // Explicit static constructor to tell C# compiler 
     // not to mark type as beforefieldinit 
     static Nested() 
     { 
     } 

     internal static readonly Singleton instance = new Singleton(); 
    } 
} 

這是此頁面上的第五個版本: http://www.yoda.arachsys.com/csharp/singleton.html

我不知道,但作者似乎認爲它的兩個線程安全的,延遲加載。

6

爲了不使用'雙重檢查鎖定mambo-jambo'或根本不實施自己的單件重新發明輪子,請使用包含在.NET 4.0 - Lazy<T>中的現成解決方案。

+0

謝謝,不知道這個+1。我在2.0雖然:) – kateroh 2011-05-13 21:32:16

1
+0

Centro已經提到了一個可能的實現使用Lazy。不管怎麼說,還是要謝謝你! – kateroh 2011-05-16 17:50:39

+0

對不起,我一定錯過了它。我實際上使用了一些比較複雜的東西來使_ensure_它的單身用作軟件許可證,而Lazy <>非常適合!但它與這個問題沒有關係。 – expelledboy 2011-05-17 09:38:35

2

你的單例初始值設定項的行爲與它應該完全相同。請參閱Raymond Chen的Lock-free algorithms: The singleton constructor

這是一個雙重檢查鎖,但沒有鎖定。在完成初始構建時,我們不要考慮鎖定,而是讓它成爲創建對象的免費人員。如果五個線程同時到達此代碼,那麼確定,我們創建五個對象。在每個人創建他們認爲是獲勝對象後,他們調用InterlockedCompareExchangePointerRelease嘗試更新全局指針。

這種技術適用於讓多個線程嘗試創建單例(並讓所有輸家銷燬其副本)的情況。如果創建單例代價昂貴或具有不需要的副作用,那麼您不希望使用free-for-all算法。

每個線程創建對象;因爲它認爲沒有人創造它。但期間InterlockedCompareExchange,只有一個線程將真的能夠全球單身人士。

獎金閱讀

0

我覺得.NET 4.0後,最簡單的方法是使用System.Lazy<T>

public class Singleton 
{ 
    private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); 

    public static Singleton Instance { get { return lazy.Value; } } 

    private Singleton() { } 
} 

喬恩斯基特有一個很好的文章here,涵蓋了很多實現單的方式和每個人的問題。

+0

這就是Centro已經給出的[相同的答案](http://stackoverflow.com/a/5997760/1838048)。 – Oliver 2015-10-30 13:52:53

1

自動屬性初始化(C#6.0)似乎不會導致您看到的單例的多個實例化。

public class Singleton 
{  
    static public Singleton Instance { get; } = new Singleton(); 
    private Singleton(); 
} 
0

請勿使用鎖定。用你的語言環境

晴簡單的線程安全的實現是:

public class Singleton 
{ 
    private static readonly Singleton _instance; 

    private Singleton() { } 

    static Singleton() 
    { 
     _instance = new Singleton(); 
    } 

    public static Singleton Instance 
    { 
     get { return _instance; } 
    } 
}