原帖位址
前面章節主要涉及了一些預備的知識,從這一章起,我們将真正開始單例模式的研究
Singleton
首先看一個最常見的單例模式的實作,也是很多人常用的一種方式:
Singleton 設計模式的下列實作采用了 Design Patterns: Elements of Reusable Object-Oriented Software[Gamma95]
中所描述的解決方案,但對它進行了修改,以便利用 C#中可用的語言功能,如屬性:
版本1:非線程安全的實作
// Bad Code ! Do not use!
public sealed class Singleton
{
static Singleton instance=null;
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}
該實作主要有兩個優點:
• 由于執行個體是在 Instance屬性方法内部建立的,是以類可以使用附加功能(例如,對子類進行執行個體化),即使它可能引入
不想要的依賴性。
• 直到對象要求産生一個執行個體才執行執行個體化;這種方法稱為"懶執行個體化"。懶執行個體化避免了在應用程式啟動時執行個體化不必要的
singleton。
但是,這種實作的主要缺點是在多線程環境下它是不安全的。
如果執行過程的不同線程同時判斷if(instance==null)語句,發現instance為null,那就可能會建立多個 Singleton對象執行個體。
解決此問題的方法有很多。
版本二:線程安全簡潔版
public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
lock (padlock)
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}
}
在第二個版本中,我們做到了線程安全。同時我們也實作了惰性加載機制。
這個版本的唯一不足之處是lock可能會對大量的并發通路的效率造成影響。但一般的應用中這樣的效率損失可以忽略不計
如果通路的效率對我們性命攸關,那我們可以改進到第三個版本
版本三:雙檢鎖(double-check locking)保證線程安全
// Bad Code ! Do not use!
public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{ //@位置1 此位置可能有多個線程處于waitzhuangtai
lock (padlock)
{
//重複判斷一次,即使在@位置1已經有多個線程湧入的情況下仍然能保證不會重複初始化Singleton
if (instance==null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
你可以對上面的代碼進行一個思考,看上去雙檢鎖機制似乎提高了通路的效率也保證了線程安全。但其實這樣的寫法在很多
平台和優化編譯器上是錯誤的。
原因在于:instance = new Singleton();這行代碼在不同編譯器上的行為是無法預知的。一個優化編譯器可以合法地如下實作
instance = new Singleton();
1. instance = 給新的實體配置設定記憶體
2. 調用Singleton的構造函數來初始化instance的成員變量((這裡的初始化指的是初始化instance的執行個體成員,因為Singleton
的靜态成員已經初始化過了)
現在想象一下有線程A和B在調用instance,線程A先進入,在執行到步驟1的時候被踢出了cpu。然後線程B進入,B看到的是
instance已經不是null了
(記憶體已經配置設定),于是它開始放心地使用instance,但這個是錯誤的,因為在這一時刻,instance的成員變量還都是預設值,
A還沒有來得及執行步驟2來完成instance的初始化。
當然編譯器也可以這樣實作:
1. temp = 配置設定記憶體
2. 調用temp的構造函數
3. instance = temp
如果編譯器的行為是這樣的話我們似乎就沒有問題了,但事實卻不是那麼簡單,因為我們無法知道某個編譯器具體是怎麼做的。
要解決這個問題,我們可以利用 記憶體牆機制(MemoryBarrier)來解決這個問題,上面的代碼改寫如下:
版本三改正版1
public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
lock (padlock)
{
if (instance==null)
{
Singleton newVal = new Singleton();
System.Threading.Thread.MemoryBarrier();
instance = newVal;
}
}
}
return instance;
}
}
}
版本三改正版2
public sealed class Singleton
{
static volatile Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
lock (padlock)
{
if (instance==null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
以上談到的一些如多線程安全、雙險鎖、記憶體牆、等概念涉及到很多編譯原理和彙編的知識,由于個人能力有限,也
沒法進一步加以詳細闡釋,大家有興趣的話參考一下以下一些網址吧:
http://msdn.microsoft.com/msdnmag/issues/05/10/MemoryModels/default.aspx
http://blogs.msdn.com/brada/archive/2004/05/12/130935.aspx
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
http://blog.joycode.com/demonfox/archive/2007/01/04/90894.aspx
http://www.cnblogs.com/dayouluo/archive/2005/12/25/304455.html
好了,到此為止,我們在第三個版本的兩個改進版中同時兼顧了效率、線程安全和惰性加載。
但這兩個改進版雖然從應用上沒有問題,但似乎又涉及了過多的專業知識,把問題複雜化了,
讓人頓生生疏之感。
既然如此,讓我們再來看一個親切一點的版本吧:
版本四:簡單實作,非惰性加載
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
這種實作簡單,線程安全,缺陷主要是instance不是惰性加載的。準确的說是不一定是惰性加載的,因為我們無法得知instance
會在什麼時候被初始化。若對我的描述有疑慮,請參考單例模式深入研究(一)