C# Singleton的使用及優缺點探討
您要在 C# 中構建應用程序。您需要只有一個實例的類,并且需要提供一個用于訪問實例的全局訪問點。您希望確保您的解決方案高效,并且能夠利用 Microsoft? .NET 公共語言運行庫功能。您可能還希望確保解決方案是線程安全的。
C# Singleton實現策略
盡管 Singleton 是一種相對簡單的模式,但是存在與具體實現有關的不同權衡因素和選項。下面是一組實現策略,及其優缺點的討論。
C# Singleton
Singleton 設計模式的下列實現采用了 Design Patterns: Elements of Reusable Object-Oriented Software [Gamma95] 中所描述的解決方案,但對它進行了修改,以便利用 C# 中可用的語言功能,如屬性:
- using System;
- public class Singleton
- {
- private static Singleton instance;
- private Singleton() {}
- public static Singleton Instance
- {
- get
- {
- if (instance == null)
- {
- instance = new Singleton();
- }
- return instance;
- }
- }
- }
該實現主要有兩個優點:
由于實例是在 Instance 屬性方法內部創建的,因此類可以使用附加功能(例如,對子類進行實例化),即使它可能引入不想要的依賴性。
直到對象要求產生一個實例才執行實例化;這種方法稱為"懶實例化"。懶實例化避免了在應用程序啟動時實例化不必要的 singleton。
但是,這種實現的主要缺點是在多線程環境下它是不安全的。如果執行過程的不同線程同時進入 Instance 屬性方法,那么可能會創建多個 Singleton 對象實例。每個線程都會執行下列語句,并決定必須創建新的實例:
- if (instance == null)
解決此問題的方法有很多。一種方法是使用被稱為 Double-Check Locking [Lea99] 的技術。而 C# 與公共語言運行庫也提供了一種"靜態初始化"方法,這種方法不需要開發人員顯式地編寫線程安全代碼,即可解決這些問題。
C# Singleton:靜態初始化
避免使用靜態初始化的原因之一是,C++ 規范在靜態變量的初始化順序方面留下了一些多義性。幸運的是,.NET Framework 通過其變量初始化處理方法解決了這種多義性:
- public sealed class Singleton
- {
- private static readonly Singleton instance = new Singleton();
- private Singleton(){}
- public static Singleton Instance
- {
- get
- {
- return instance;
- }
- }
- }
在此策略中,將在***次引用類的任何成員時創建實例。公共語言運行庫負責處理變量初始化。該類標記為 sealed 以阻止發生派生,而派生可能會增加實例。有關將類標記為 sealed 的利與弊的討論,請參閱 [Sells03]。此外,變量標記為 readonly,這意味著只能在靜態初始化期間(此處顯示的示例)或在類構造函數中分配變量。
該實現與前面的示例類似,不同之處在于它依賴公共語言運行庫來初始化變量。它仍然可以用來解決 Singleton 模式試圖解決的兩個基本問題:全局訪問和實例化控制。公共靜態屬性為訪問實例提供了一個全局訪問點。此外,由于構造函數是私有的,因此不能在類本身以外實例化 Singleton 類;因此,變量引用的是可以在系統中存在的唯一的實例。
由于 Singleton 實例被私有靜態成員變量引用,因此在類***被對 Instance 屬性的調用所引用之前,不會發生實例化。因此,與 Design Patterns 形式的 Singleton 一樣,該解決方案實現了懶實例化屬性的一種形式。
這種方法唯一的潛在缺點是,您對實例化機制的控制權較少。在 Design Patterns 形式中,您能夠在實例化之前使用非默認的構造函數或執行其他任務。由于在此解決方案中由 .NET Framework 負責執行初始化,因此您沒有這些選項。在大多數情況下,靜態初始化是在 .NET 中實現 Singleton 的***方法。
多線程 Singleton
靜態初始化適合于大多數情形。如果您的應用程序必須延遲實例化、在實例化之前使用非默認的構造函數或執行其他任務、并且工作在多線程環境中,那么您需要另一種解決方案。但是,在一些情況下,您無法像在"靜態初始化"示例中那樣依賴公共語言運行庫來確保線程的安全性。在這種情況下,必須使用特定的語言功能來確保在存在多線程的情況下僅創建一個對象實例。更常見的解決方案之一是使用 Double-Check Locking [Lea99] 技術來阻止不同的線程同時創建 singleton 的新實例。
注意:公共語言運行庫解決了在其他環境中常見的、與使用 Double-Check Locking 有關的問題。
下面的實現僅允許一個線程在尚未創建 Singleton 實例的情況下進入關鍵區域(該區域由 lock 塊標識)。
- using System;
- public sealed class Singleton
- {
- private static volatile Singleton instance;
- private static object syncRoot = new Object();
- private Singleton() {}
- public static Singleton Instance
- {
- get
- {
- if (instance == null)
- {
- lock (syncRoot)
- {
- if (instance == null)
- instance = new Singleton();
- }
- }
- return instance;
- }
- }
- }
此方法確保了僅在需要實例時才會創建僅一個實例。此外,變量被聲明為 volatile,以確保只有在實例變量分配完成后才能訪問實例變量。***,此方法使用 syncRoot 實例來進行鎖定(而不是鎖定類型本身),以避免發生死鎖。
此 double-check locking 方法解決了線程并發問題,同時避免在每個 Instance 屬性方法的調用中都出現獨占鎖定。它還允許您將實例化延遲到***次訪問對象時發生。實際上,應用程序很少需要這種類型的實現。大多數情況下,靜態初始化方法已經夠用。
C# Singleton優缺點
優點
由于 .NET Framework 顯式地指定靜態變量初始化如何以及何時發生,因此靜態初始化方法是可能的。
列的前面的"多線程 Singleton"中所描述的 Double-Check Locking 技術已在公共語言運行庫中正確實現。
缺點
如果您的多線程應用程序需要進行顯式初始化,那么必須采取措施以避免線程問題。
【編輯推薦】