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