1. 程式人生 > 程式設計 >C#實現單例模式的幾種方法總結

C#實現單例模式的幾種方法總結

介紹

單例模式是軟體工程學中最富盛名的設計模式之一。從本質上看,單例模式只允許被其自身例項化一次,且向外部提供了一個訪問該例項的介面。通常來說,單例物件進行例項化時一般不帶引數,因為如果不同的例項化請求傳遞的引數不同的話會導致問題的產生。(若多個請求都是傳遞的同樣的引數的話,工廠模式更應該被考慮)

C#中實現單例有很多種方法,本文將按順序介紹非執行緒安全、完全懶漢式、執行緒安全和低/高效能集中版本。

在所有的實現版本中,都有以下幾個共同點:

  • 唯一的、私有的且無參的建構函式,這樣不允許外部類進行例項化;
  • 類是密封的,儘管這不是強制的,但是嚴格來講從上一點來看密封類能有助於JIT的優化;
  • 一個靜態變數應該指向類的唯一例項;
  • 一個公共的靜態變數用於獲得這個類的唯一例項(如果需要,應該建立它);

需要注意的是,本文中所有的例子中都是用一個 public static Instance的變數來訪問單例類例項,要將其轉換成公共函式是很容易的,但是這樣並不會帶來效率和執行緒安全上的提升。

Version 1 - 非執行緒安全

/// <summary>
/// Bad code!Do not use! 
/// </summary>
public sealed class Singleton
{
 private static Singleton instance = null;
 private Singleton() { }
 public static Singleton Instance
 {
  get
  {
   if (instance == null)
   {
    instance = new Singleton();
   }
   return instance;
  }
 }
}

該版本在多執行緒下是不安全的,會建立多個例項,請不要在生產環境中使用!

因為如果兩個執行緒同時執行到if(instance==null)判斷時,就會建立兩個例項,這是違背單例模式的初衷的。實際上在後面那個執行緒進行判斷是已經生成了一個例項,但是對於不同的執行緒來說除非進行了執行緒間的通訊,否則它是不知道的。

Version 2 - 簡單的執行緒安全

public sealed class Singleton2
{
 private static Singleton2 instance = null;
 private static readonly object obj = new object();
 private Singleton2() { }
 public Singleton2 Instance
 {
  get
  {
   lock (obj)
   {
    if (instance == null)
    {
     instance = new Singleton2();
    }
    return instance;
   }
  }
 }
}

該版本是執行緒安全的。通過對一個過執行緒共享的物件進行加鎖操作,保證了在同一時刻只有一個執行緒在執行lock{}裡的程式碼。當第一個執行緒在進行instance判斷或建立時,後續執行緒必須等待直到前一執行緒執行完畢,因此保證了只有第一個執行緒能夠建立instance例項。

但不幸的是,因為每次對instance的請求都會進行lock操作,其效能是不佳的。

需要注意的是,這裡使用了一個private static object變數進行鎖定,這是因為當如果對一個外部類可以訪問的物件進行鎖定時會導致效能低下甚至死鎖。因此通常來說為了保證執行緒安全,進行加鎖的物件應該是private的。

Version 3 - Double-check locking的執行緒安全

/// <summary>
/// Bad code ! Do not use!
/// </summary>
public sealed class Singleton3
{
 private static Singleton3 instance = null;
 private static object obj = new object();
 private Singleton3() { }
 public static Singleton3 Instance
 {
  get
  {
   if (instance == null)
   {
    lock (obj)
    {
     if (instance == null)
     {
      instance = new Singleton3();
     }
    }
   }
   return instance;
  }
 }
}

該版本中試圖去避免每次訪問都進行加鎖操作並實現執行緒安全。然後,這段程式碼對Java不起作用,因Java的記憶體模型不能保證在建構函式一定在其他物件引用instance之前完成。還有重要的一點,它不如後面的實現方式。

Version 4 - 不完全懶漢式,但不加鎖的執行緒安全

public sealed class Singleton4
{
 private static readonly Singleton4 instance = new Singleton4();
 /// <summary>
 /// 顯式的靜態建構函式用來告訴C#編譯器在其內容例項化之前不要標記其型別
 /// </summary>
 static Singleton4() { }
 private Singleton4() { }
 public static Singleton4 Instance
 {
  get
  {
   return instance;
  }
 }
}

這個版本是的實現非常的簡單,但是卻又是執行緒安全的。C#的靜態建構函式只有在當其類的例項被建立或者有靜態成員被引用時執行,在整個應用程式域中只會被執行一次。使用當前方式明顯比前面版本中進行額外的判斷要快。

當然這個版本也存在一些瑕疵:

不是真正意義上的懶漢模式(需要的時候才建立例項),若單例類還存在其他靜態成員,當其他類第一次引用這些成員時便會建立該instance。下個版本實現會修正這個問題;

只有.NET中才具有beforefieldinit特性,即懶漢式實現。且在.Net 1.1以前的編譯器不支援,不過這個現在來看問題不大;

所有版本中,只有這裡將instance設定成了readonly,這不僅保證了程式碼的高校且顯得十分短小。

Version 5 - 完全懶漢例項化

public sealed class Singleton5
{
 private Singleton5() { }
 public static Singleton5 Instance { get { return Nested.instance; } }
 private class Nested
 {
  //Explicit static constructor to tell C# compiler
  //not to mark type as beforefieldinit
  static Nested()
  {
  }
  internal static readonly Singleton5 instance = new Singleton5();
 }
}

該版本看起來稍微複雜難懂,其實只是在寫法上實現了上一版本的瑕疵,通過內嵌類的方式先實現了只有在真正應用Instance時才進行例項化。其效能表現與上一版本無異。

Version 6 - 使用.NET 4 Lazy<T> type 特性

public sealed class Singleton6
{
 private static readonly Lazy<Singleton6> lazy =
   new Lazy<Singleton6>(()=> new Singleton6());
 public static Singleton6 Instance { get { return lazy.Value; } }
 private Singleton6() { }
}

如果你使用的是.NET 4或其以上版本,可以使用System.Lazy<T> type來實現完全懶漢式。其程式碼看起來也很簡潔且效能表現也很好。

效能 VS 懶漢式

一般情況下,我們並不需要實現完全懶漢式,除非你的構造初始化執行了某些費時的工作。因此一般的,我們使用顯式的靜態建構函式就能夠適用。

本文翻譯自Implementing the Singleton Pattern in C#,作者在文中做了一些迴圈測試,具體的讀者可直接閱讀原文。

Exception

有時候在進行建構函式初始化時可能 會丟擲異常,但這對整個應用程式來說不應該是致命的,所以可能的情況下,你應該自己處理這種異常情況。

總結

上述提供的幾種實現方法中,一般情況下提倡使用Version 4,除非遇到有時早於單列類例項化時就引用了其他靜態成員。這種情況下,Version 2一旦被考慮,雖然它看起來會因加鎖耗時,但是其實執行起來並沒有你想的那麼慢,關鍵是你很容易寫對它。

顯然Version 1你永遠都不應該考慮,Version 3在與Version 5的對比下也是不在考慮範圍之內的。

到此這篇關於C#實現單例模式的文章就介紹到這了,更多相關C#實現單例模式內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!