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# 與公共語言運行庫也提供了一種"靜態初始 化"方法,這種方法不需要開發人員顯式地編寫線程安全代碼,即可解決這 些問題。
靜態初始化
One of the reasons Design Patterns [Gamma95] 避免使用靜態初始化的原因之一是,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 技術已在公共語言運行庫中正確實現。
缺點
如果 您的多線程應用程序需要進行顯式初始化,那麼必須采取措施以避免線程問題。