c#中單例模式詳解

少年真愛發表於2023-11-02

基礎介紹:

 確保一個類只有一個例項,並提供一個全域性訪問點。

  適用於需要頻繁例項化然後銷燬的物件,建立物件消耗資源過多,但又經常用到的物件,頻繁訪問資料庫或檔案的物件。

  其本質就是保證在整個應用程式生命週期中,任何一個時刻,單例類的例項都只存在一個

  • 特性和功能:確保一個類只有一個例項,並提供一個全域性訪問點。
  • 使用環境:當類只需要一個例項,且易於訪問,且例項應在整個應用程式中共享時。
  • 注意事項:需要注意執行緒安全問題。
  • 優點:可以確保一個類只有一個例項,減少了記憶體開銷。
  • 缺點:沒有介面,擴充套件困難。  

應用場景:

  單例模式通常適用於在整個應用程式中只需要一個例項化物件的場景,以確保資源的高效利用和應用程式的穩定性。(共享資源)

  資源共享的情況下,避免由於資源操作時導致的效能或損耗等。

  控制資源的情況下,方便資源之間的互相通訊。如執行緒池等。

  • 日誌系統:在應用程式中,通常只需要一個日誌系統,以避免在多個地方建立多個日誌物件。這一般是由於共享的日誌檔案一直處於開啟狀態,所以只能有一個例項去操作,否則內容不好追加也有可能造成資源佔用加劇資源消耗。
  • 資料庫連線池:在應用程式中,資料庫連線池是一個非常重要的資源,單例模式可以確保在應用程式中只有一個資料庫連線池例項,避免資源浪費。主要是節省開啟或者關閉資料庫連線所引起的效率損耗,因為何用單例模式來維護,就可以大大降低這種損耗。
  • 配置檔案管理器:在應用程式中,通常只需要一個配置檔案管理器來管理應用程式的配置檔案,單例模式可以確保在整個應用程式中只有一個配置檔案管理器例項。這個是由於配置檔案是共享的資源。
  • 快取系統:在應用程式中,快取系統是一個重要的元件,單例模式可以確保在整個應用程式中只有一個快取例項,以提高應用程式的效能。
  • 網站線上人數統計:其實就是全域性計數器,也就是說所有使用者在相同的時刻獲取到的線上人數數量都是一致的。
  • GUI元件:在圖形使用者介面(GUI)開發中,單例模式可以確保在整個應用程式中只有一個GUI元件例項,以確保使用者介面的一致性和穩定性。

建立方式:

  餓漢式:類載入就會導致該單例項物件被建立。(靜態變數方式、靜態程式碼塊方式)

  懶漢式:類載入不會導致該單例項物件被建立,而是首次使用該物件時才會建立。(執行緒不安全型、執行緒安全型、雙重檢查鎖)

 

  1. 懶漢式---非執行緒安全型

     1 public class Singleton
     2     {
     3         //定義一個私有的靜態全域性變數來儲存該類的唯一例項
     4         private static Singleton singleton;
     5 
     6         /// <summary>
     7         /// 建構函式
     8         /// </summary>
     9         private Singleton()
    10         {
    11             //必須是私有的建構函式,這樣就可以保證該類無法透過new來建立該類的例項。
    12             //想要使用該類只能透過唯一訪問點GetInstance()。
    13         }
    14 
    15         /// <summary>
    16         /// 全域性訪問點
    17         /// 設定為靜態方法則可在外邊無需建立該類的例項就可呼叫該方法
    18         /// </summary>
    19         /// <returns></returns>
    20         public static Singleton GetInstance()
    21         {
    22             if (singleton == null)
    23             {
    24                 singleton = new Singleton();
    25             }
    26             return singleton;
    27         }
    28     }

    上面的程式碼中,由於建構函式被設定為 private 了,無法再在 Singleton 類的外部使用 new 來例項化一個例項,只能透過訪問 GetInstance()來訪問 Singleton 類。

    GetInstance()透過如下方式保證該 Singleton 只存在一個例項:

    首先這個 Singleton 類會在在第一次呼叫 GetInstance()時建立一個例項(第24行),並將這個例項的引用封裝在自身類中的靜態全域性變數singleton(第4行)

    然後以後呼叫 GetInstance()時就會判斷這個 Singleton 是否存在一個例項了(第22行),如果存在,則不會再建立例項。

    這樣就實現了懶載入的效果。但是,如果是多執行緒環境,會出現執行緒安全問題。

    比如多個執行緒同時執行GetInstance()方法時都走到了第22行,這個時候一個執行緒進入 if 判斷語句後但還沒有例項化 Singleton 時,第二個執行緒到達,此時 singleton 還是為 null

    如此會造成多個執行緒都會進入 if 執行程式碼塊中即都會執行第24行,這樣的話,就會建立多個例項違背了單裡模式,因此引出了例項2執行緒安全型。

     

  2. 懶漢式---執行緒安全型

     1 public class Singleton
     2     {
     3         //定義一個私有的靜態全域性變數來儲存該類的唯一例項
     4         private static Singleton singleton;
     5 
     6         //執行緒鎖
     7         private static readonly object _Object = new object();
     8 
     9         /// <summary>
    10         /// 建構函式
    11         /// </summary>
    12         private Singleton()
    13         {
    14             //必須是私有的建構函式,這樣就可以保證該類無法透過new來建立該類的例項。
    15             //想要使用該類只能透過唯一訪問點GetInstance()。
    16         }
    17 
    18         /// <summary>
    19         /// 全域性訪問點
    20         /// 設定為靜態方法則可在外邊無需建立該類的例項就可呼叫該方法
    21         /// </summary>
    22         /// <returns></returns>
    23         public static Singleton GetInstance()
    24         {
    25             lock (_Object)
    26             {
    27                 if (singleton == null)
    28                 {
    29                     singleton = new Singleton();
    30                 }
    31             }
    32             return singleton;
    33         }
    34     }

    相比例項1中可以看到在類中有定義了一個靜態的只讀物件  _Object(第7行),該物件主要是提供給lock 關鍵字使用。

    lock關鍵字引數必須為基於引用型別的物件,該物件用來定義鎖的範圍。

    當多個執行緒同時進入GetInstance()方法時,由於存在鎖機制,當一個執行緒進入lock程式碼塊時,其餘執行緒會在lock語句的外部等待。

    當第一個執行緒執行完第29行建立物件例項後,便會退出鎖定區域,這個時候singleton變數已經不為null了。

    所以餘下執行緒再次進入lock程式碼塊時,由於第27行的原因則不會再次建立物件的例項。

    但這裡就涉及一個效能問題了,每一次有執行緒進入 GetInstance()時,均會執行鎖定操作來實現執行緒同步,這是非常耗費效能的。

    解決這個問題也很簡單,進行雙重檢查鎖定判斷即例項3。

     

  3. 懶漢式---雙重檢查鎖

     1 public class Singleton
     2     {
     3         //定義一個私有的靜態全域性變數來儲存該類的唯一例項
     4         private static Singleton singleton;
     5 
     6         //執行緒鎖
     7         private static readonly object _Object = new object();
     8 
     9         /// <summary>
    10         /// 建構函式
    11         /// </summary>
    12         private Singleton()
    13         {
    14             //必須是私有的建構函式,這樣就可以保證該類無法透過new來建立該類的例項。
    15             //想要使用該類只能透過唯一訪問點GetInstance()。
    16         }
    17 
    18         /// <summary>
    19         /// 全域性訪問點
    20         /// 設定為靜態方法則可在外邊無需建立該類的例項就可呼叫該方法
    21         /// </summary>
    22         /// <returns></returns>
    23         public static Singleton GetInstance()
    24         {
    25             if (singleton == null)//第一重
    26             {
    27                 lock (_Object)
    28                 {
    29                     if (singleton == null)//第二重
    30                     {
    31                         singleton = new Singleton();
    32                     }
    33                 }
    34             }
    35             return singleton;
    36         }
    37     }

    相比例項2來看,只是增加了第25行。

    在多執行緒中,當第一個執行緒建立完物件的例項後,singleton變數已經不為null了。之後再訪問GetInstance()方法時,將不會再進行lock等待。

    如果沒有這行的情況下,每次多執行緒同時進入GetInstance()方法時,多餘的執行緒都會進入lock進行等待。這是非常耗費效能的。

    相比呼叫GetInstance()方法來作為全域性訪問點還有另外一種寫法:

     1  public class Singleton
     2     {
     3         private static Singleton instance;
     4 
     5         private Singleton() { }
     6 
     7         public static Singleton Instance
     8         {
     9             get
    10             {
    11                 if (instance == null)
    12                 {
    13                     instance = new Singleton();
    14                 }
    15                 return instance;
    16             }
    17         }
    18     }

    前三個例項在客戶端呼叫:Singleton singletonOne = Singleton.GetInstance();

    後一種則可以直接:Singleton.Instance進行使用。

     

  4. 餓漢式

     1 public sealed class Singleton
     2     {
     3         //定義一個私有靜態的只讀的全域性變數
     4         private static readonly Singleton singleton = new Singleton();
     5 
     6         /// <summary>
     7         /// 建構函式
     8         /// </summary>
     9         private Singleton()
    10         {
    11             //必須是私有的建構函式,這樣就可以保證該類無法透過new來建立該類的例項。
    12             //想要使用該類只能透過唯一訪問點GetInstance()。
    13         }
    14 
    15         /// <summary>
    16         /// 全域性訪問點
    17         /// 設定為靜態方法則可在外邊無需建立該類的例項就可呼叫該方法
    18         /// </summary>
    19         /// <returns></returns>
    20         public static Singleton GetInstance()
    21         {
    22             return singleton;
    23         }
    24     }

    在c#中使用靜態初始化時無需顯示地編寫執行緒安全程式碼,C# 與 CLR 會自動解決前面提到的懶漢式單例類時出現的多執行緒同步問題。

    當整個類被載入的時候,就會自行初始化 singleton 這個靜態只讀變數。

    而非在第一次呼叫 GetInstance()時再來例項化單例類的唯一例項,所以這就是一種餓漢式的單例類。

總結:

  Singleton(單例):在單例類的內部實現只生成一個例項,同時它提供一個靜態的getInstance()工廠方法,讓客戶可以訪問它的唯一例項;為了防止在外部對其例項化,將其建構函式設計為私有;在單例類內部定義了一個Singleton型別的靜態物件,作為外部共享的唯一例項。

  (1)資源共享的情況下,避免由於資源操作時導致的效能或損耗等。如日誌檔案,應用配置。

  (2)控制資源的情況下,方便資源之間的互相通訊。如執行緒池等。

 

 

 

  

  

相關文章