設計模式 之 單例模式(MSDN)
在 C# 中實現 Singleton
發佈日期 : 4/1/2004 | 更新日期 : 4/19/2004
使用 Microsoft .NET 的企業解決方案模式 > 分佈式系統模式 > 在 C# 中實現 Singleton
版本: 1.0.1
本頁內容
上下文
實現策略
結果上下文
致謝
上下文
您要在 C# 中構建應用程序。您需要只有一個實例的類,並且需要提供一個用於訪問實例的全局訪問點。您希望確保您的解決方案高效,並且能夠利用 Microsoft? .NET 公共語言運行庫功能。您可能還希望確保解決方案是線程安全的。
返回頁首
實現策略
儘管 Singleton 是一種相對簡單的模式,但是存在與具體實現有關的不同權衡因素和選項。下面是一組實現策略,及其優缺點的討論。
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# 與公共語言運行庫也提供了一種"靜態初始化"方法,這種方法不需要開發人員顯式地編寫線程安全代碼,即可解決這些問題。
靜態初始化
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 有關的問題。有關這些問題的詳細信息,請參閱馬里蘭大學計算機科學系網站中的"The 'Double-Checked Locking Is Broken' Declaration",網址爲 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html.
下面的實現僅允許一個線程在尚未創建 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 技術已在公共語言運行庫中正確實現。
缺點
如果您的多線程應用程序需要進行顯式初始化,那麼必須採取措施以避免線程問題。
返回頁首
致謝
[Gamma95] Gamma, Helm, Johnson, and Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.
[Lea99] Lea, Doug. Concurrent Programming in Java, Second Edition. Addison-Wesley, 1999.
[Sells03] Sells, Chris. "Sealed Sucks." sellsbrothers.com News. Available at: http://www.sellsbrothers.com/news/showTopic.aspx?ixTopic=411.
Note: Despite its title, the "Sealed Sucks" article is actually a balanced discussion of the pros and cons of marking a class sealed.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.