天天看點

單例模式深入研究

原帖位址

前面章節主要涉及了一些預備的知識,從這一章起,我們将真正開始單例模式的研究

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

會在什麼時候被初始化。若對我的描述有疑慮,請參考單例模式深入研究(一)