單例模式 單例模式(Singleton)

原本我本身寫了一篇,就在發佈的時候,博客園也不知道怎麼抽抽了,尼瑪,我寫的那麼久的東西整個都沒有了,就留了一句「什麼是單例模式呢」,汝妹!不開森!!!這件事情告訴我,之後要如今Word活着其餘編輯器上編輯好再複製到博文,否則就是白費了一腔心血。html

如下文章轉載自:單例模式(Singleton),例子我本身也寫了,但與這篇文章也是差很少同樣的。由於上述緣由,不想再寫一遍了,就轉載了這一篇我以爲看起來輕鬆好理解的文章。安全

簡單說來,單例模式(也叫單件模式)的做用就是保證在整個應用程序的生命週期中,任何一個時刻,單例類的實例都只存在一個(固然也能夠不存在)。多線程

下面來看單例模式的結構圖(圖太簡單了)編輯器

image

從上面的類圖中能夠看出,在單例類中有一個構造函數 Singleton ,可是這個構造函數倒是私有的(前面是「 - 」符號),而後在裏面還公開了一個 GetInstance()方法,函數

經過上面的類圖不難看出單例模式的特色,從而也能夠給出單例模式的定義post

單例模式保證一個類僅有一個實例,同時這個類還必須提供一個訪問該類的全局訪問點。性能

先來將 Singleton 寫出來再說編碼

    public class Singleton
    {
        private static Singleton singleton;
          
        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            if (singleton == null)
            {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

調用:url

    class Program
    {
        static void Main(string[] args)
        {
            Singleton a = Singleton.GetInstance();
            Singleton b = Singleton.GetInstance();
            if (a.Equals(b))
            {
                System.Console.WriteLine("實例確實相同");
                System.Console.ReadLine();
            }
        }
    }

  運行結果爲spa

從上面的結果能夠看出來,儘管我兩次訪問了 GetInstance(),可是我訪問的只是同一個實例,換句話來講,上面的代碼中,因爲構造函數被設置爲 private 了,因此您沒法再在 Singleton 類的外部使用 new 來實例化一個實例,您只能經過訪問 GetInstance()來訪問 Singleton 類,

GetInstance()經過以下方式保證該 Singleton 只存在一個實例:首先這個 Singleton 類會在在第一次調用 GetInstance()時建立一個實例,並將這個實例的引用封裝在自身類中,而後之後調用 GetInstance()時就會判斷這個 Singleton 是否存在一個實例了,若是存在,則不會再建立實例。而是調用之前生成的類的實例,這樣下來,整個應用程序中便就只存在一個實例了。

從這裏再來總結單例模式的特色:

首先,單例模式使類在程序生命週期的任什麼時候刻都只有一個實例,

而後,單例的構造函數是私有的,外部程序若是想要訪問這個單例類的話,

必須經過 GetInstance()來請求(注意是請求)獲得這個單例類的實例。    

                             

有的時候,老是容易把全局變量和單例模式給弄混了,下面就剖析一下全局變量和單例模式相比的缺點

首先,全局變量呢就是對一個對象的靜態引用,全局變量確實能夠提供單例模式實現的全局訪問這個功能,

可是,它並不能保證您的應用程序中只有一個實例,同時,在編碼規範中,也明確指出,

應該要少用全局變量,由於過多的使用全局變量,會形成代碼難讀,

還有就是全局變量並不能實現繼承(雖然單例模式在繼承上也不能很好的處理,可是仍是能夠實現繼承的)

而單例模式的話,其在類中保存了它的惟一實例,這個類,它能夠保證只能建立一個實例,

同時,它還提供了一個訪問該惟一實例的全局訪問點。

                      

下面來看一種狀況(這裏先假設個人應用程序是多線程應用程序),同時仍是之前面的 Demo 來作爲說明,

若是在一開始調用 GetInstance()時,是由兩個線程同時調用的(這種狀況是很常見的),注意是同時,(或者是一個線程進入 if 判斷語句後但尚未實例化 Singleton 時,第二個線程到達,此時 singleton 仍是爲 null),兩個線程均會進入 GetInstance(),然後因爲是第一次調用 GetInstance(),存儲在 Singleton 中的靜態變量 singleton== null ,兩個線程都可經過 if 語句的條件判斷,會建立兩個實例,很顯然,這便違法了單例模式的初衷了,

那麼如何解決上面出現的這個問題(即多線程下使用單例模式時有可能會建立多個實例這一現象)呢?

其實,這個是很好解決的,

因爲上面出現的問題中涉及到多個線程同時訪問這個 GetInstance(),那麼您能夠先將一個線程鎖定,而後等這個線程完成之後,再讓其餘的線程訪問 GetInstance()中的 if 段語句,

好比,有兩個線程同時到達若是 singleton != null 的話,那麼上面提到的問題是不會存在的,由於已經存在這個實例了,全部的線程都沒法進入 if 語句塊,

也就是全部的線程都沒法調用語句 new Singleton()了,這樣仍是能夠保證應用程序生命週期中的實例只存在一個,

可是若是此時的 singleton == null 的話,那麼意味着這兩個線程都是能夠進入這個 if 語句塊的,那麼就有可能出現上面出現的單例模式中有多個實例的問題,

此時,我可讓一個線程先進入 if 語句塊,而後我在外面對這個 if 語句塊加鎖,對第二個線程呢,因爲 if 語句進行了加鎖處理,因此這個進程就沒法進入 if 語句塊而處於阻塞狀態,

當進入了 if 語句塊的線程完成 new  Singleton()後,這個線程便會退出 if 語句塊,此時,第二個線程就從阻塞狀態中恢復,即就能夠訪問 if 語句塊了,可是因爲前面的那個線程已近建立了 Singleton 的實例,因此 singleton != null ,此時,第二個線程便沒法經過 if 語句的判斷條件了,即沒法進入 if 語句塊了,這樣便保證了整個生命週期中只存在一個實例,也就是隻有第一個線程建立了 Singleton 實例,第二個線程則沒法建立實例。

下面就來從新改進前面 Demo 中的 Singleton 類,使其在多線程的環境下也能夠實現單例模式的功能。

    public class Singleton
    {
        // 定義一個static的全局變量來保存該類惟一實例
        private static Singleton singleton;

        //該對象在程序運行時建立
        private static readonly object syncObject = new object();

        // private, 確保外部調用時不能用new來建立實例
        private Singleton()
        {
        }

        // 定義一個static的全局訪問點,確保外部無需實例化便可調用
        public static Singleton GetInstance()
        {
            // 保證只在第一次調用時實例化一次
            if (singleton == null)
            {
                lock (syncObject)//鎖定
                {
                    if (singleton == null)
                    {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

 

上面的就是改進後的代碼,能夠看到在類中有定義了一個靜態的只讀對象  syncObject,爲什麼還要建立一個 syncObject 靜態只讀對象呢?

因爲提供給 lock 關鍵字的參數必須爲基於引用類型的對象,該對象用來定義鎖的範圍,因此這個引用類型的對象不能爲 null ,而開始的時候,singleton 爲 null,沒法實現加鎖,必需要再建立一個對象即 syncObject 來定義加鎖的範圍。

爲何要在 if 語句中使用兩次判斷 singleton == null這裏涉及到一個名詞 Double-Check Locking ,也就是雙重檢查鎖定,爲什麼要使用雙重檢查鎖定呢

考慮這樣一種狀況,就是有兩個線程同時到達,即同時調用 GetInstance(),此時因爲 singleton == null ,因此很明顯,兩個線程均可以經過第一重的 singleton == null ,進入第一重 if 語句後,因爲存在鎖機制,因此會有一個線程進入 lock 語句並進入第二重 singleton == null ,而另外的一個線程則會在 lock 語句的外面等待。當第一個線程執行完 new  Singleton()語句後,便會退出鎖定區域,此時,第二個線程即可以進入 lock 語句塊,此時,若是沒有第二重 singleton == null 的話,那麼第二個線程仍是能夠調用 new  Singleton()語句,這樣第二個線程也會建立一個 Singleton 實例,這樣也仍是違背了單例模式的初衷的,因此這裏必需要使用雙重檢查鎖定。

細心的朋友必定會發現,若是我去掉第一重 singleton == null ,程序仍是能夠在多線程下無缺的運行的,考慮在沒有第一重 singleton == null 的狀況下,當有兩個線程同時到達,此時,因爲 lock 機制的存在,第一個線程會進入 lock 語句塊,而且能夠順利執行 new Singleton(),當第一個線程退出 lock 語句塊時, singleton 這個靜態變量已不爲 null 了,因此當第二個線程進入 lock 時,仍是會被第二重 singleton == null 擋在外面,而沒法執行 new Singleton(),因此在沒有第一重 singleton == null 的狀況下,也是能夠實現單例模式的?那麼爲何須要第一重 singleton == null 呢?

這裏就涉及一個性能問題了,由於對於單例模式的話,new Singleton()只須要執行一次就 OK 了,而若是沒有第一重 singleton == null 的話,每一次有線程進入 GetInstance()時,均會執行鎖定操做來實現線程同步,這是很是耗費性能的,而若是我加上第一重 singleton == null 的話,那麼就只有在第一次,也就是 singleton ==null 成立時的狀況下執行一次鎖定以實現線程同步,而之後的話,便只要直接返回 Singleton 實例就 OK 了而根本無需再進入 lock 語句塊了,這樣就能夠解決由線程同步帶來的性能問題了。

好,關於多線程下單例模式的實現的介紹就到這裏了,可是,關於單例模式的介紹還沒完。                

下面將要介紹的是懶漢式單例和餓漢式單例

懶漢式單例

何爲懶漢式單例呢,能夠這樣理解,懶漢式呢,就是這個單例類的這個惟一實例是在第一次使用 GetInstance()時實例化的,若是您不調用 GetInstance()的話,這個實例是不會存在的,即爲 null 。形象點說呢,就是你不去動它的話,它本身是不會實例化的,因此能夠稱之爲懶漢。其實呢,我前面在介紹單例模式的這幾個 Demo 中都是使用的懶漢式單例,

看下面的 GetInstance()方法就明白了:

public static Singleton GetInstance()
        {
            // 保證只在第一次調用時實例化一次
            if (singleton == null)
            {
                lock (syncObject)//鎖定
                {
                    if (singleton == null)
                    {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }

從上面的這個 GetInstance()中能夠看出這個單例類的惟一實例是在第一次調用 GetInstance()時實例化的,因此此爲懶漢式單例。         

餓漢式單例

懶漢式單例因爲人懶,因此其本身是不會主動實例化單例類的惟一實例的,而餓漢式的話,則恰好相反,其因爲肚子餓了,因此處處找東西吃,人也變得主動了不少,因此根本就不須要別人來催他實例化單例類的爲一實例,其本身就會主動實例化單例類的這個惟一類。在 C# 中,能夠用特殊的方式實現餓漢式單例,即便用靜態初始化來完成餓漢式單例模式

 

        public sealed class Singleton
        {
            private static readonly Singleton singleton = new Singleton();

            private Singleton() { }

            public static Singleton GetInstance() { return singleton; }
        }  

 

要先在這裏提一下的是使用靜態初始化的話,無需顯示地編寫線程安全代碼,C# 與 CLR 會自動解決前面提到的懶漢式單例類時出現的多線程同步問題。上面的餓漢式單例類中能夠看到,當整個類被加載的時候,就會自行初始化 singleton 這個靜態只讀變量。而非在第一次調用 GetInstance()時再來實例化單例類的惟一實例,因此這就是一種餓漢式的單例類。

           

好,到這裏,就真正的把單例模式介紹完了,在此呢再總結一下單例類須要注意的幾點:

1、單例模式是用來實如今整個程序中只有一個實例的。

2、單例類的構造函數必須爲私有,同時單例類必須提供一個全局訪問點。

3、單例模式在多線程下的同步問題和性能問題的解決。

4、懶漢式和餓漢式單例類。

5、C# 中使用靜態初始化實現餓漢式單例類。

相關文章
相關標籤/搜索