Head First設計模式——單例模式

單例模式是全部設計模式中最簡單的模式,也是咱們日常常常用到的,單例模式一般被咱們應用於線程池、緩存操做、隊列操做等等。設計模式

單例模式旨在建立一個類的實例,建立一個類的實例咱們用全局靜態變量或者約定也能辦到單例的做用,爲何咱們要用單例模式?緩存

接下來咱們就從如何造成單例模式,單例模式建立的過程來說解。多線程

一、單例如何造成

咱們日常建立一個對象須要new對象,假若有一個對象ObjectClass咱們實例化它。函數

new ObjectClass()

若是另一個類要使用ObjectClass則能夠再經過new來建立另一個實例化,若是這個類是public 則咱們能夠在使用的時候屢次實例化對象。性能

那咱們怎麼保證類不被其餘類實例化,利用private關鍵字咱們能夠採用私有構造函數來阻止外部實例化該類。測試

public class ObjectClass
{
   private ObjectClass()
    {
    }    
}

這樣一來咱們沒法實例化ObjectClass則咱們就沒法使用它。那咱們要怎麼實例化呢?優化

因爲私有構造方法咱們只能在內部訪問,因此咱們能夠用一個內部方法實例化ObjectClass,爲了外部可以訪問這個方法咱們將這個方法設置成static。spa

這樣作了以後確保返回對象始終是第一次建立的對象,咱們用一個私有靜態對象來存儲實例化的對象,若是對象沒建立咱們則當即建立,若是已經建立就返回已經建立的對象。線程

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

        public static ObjectClass GetSingletone()
        {
            if (singleton == null)
            {
                singleton = new ObjectClass();
            }
            return singleton;
        }
    }

到這裏咱們的單例模式就造成了,單例模式定義:設計

單例模式:確保一個類只有一個實例,並提供一個全局訪問點。

二、多線程致使單例模式問題

啓用多線程測試單例返回對象

    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                TestSingleton();
            }
            Console.ReadKey();
        }

        public static void TestSingleton()
        {
            Task.Factory.StartNew(new Action(() =>
            {
                var hc = ObjectClass.GetSingletone().GetHashCode();
                Console.WriteLine(hc);
            }));
        }
    }

  

如圖中作的測試同樣,我啓了10個線程得到單例對象而後打印對象的HashCode。測試發現有HashCode不一致的狀況,證實單例返回的對象並非只有一個。

由於多線程運行的時候可能會同時進入if (singleton == null)的判斷,若是此時singleton變量還沒被實例化則可能有多個線程進入到實例化代碼,以致於返回的實例化對象不是同一個。

三、解決多線程單例問題

因爲多線程致使if檢查變量問題,則爭對檢查問題咱們能夠有兩類解決辦法:

①"急切"建立實例,不用延遲實例化作法

急切實例化就是在靜態初始化器中建立對象,這樣就保證了程序運行階段單例對象已經建立好,去除if判斷。

    public class ObjectClass
    {
        private static ObjectClass singleton=new ObjectClass();
        private ObjectClass()
        {
        }

        public static  ObjectClass GetSingletone()
        {
            return singleton;
        }
    }

加鎖

爲了讓建立對象只能有一個線程操做,則咱們對建立對象代碼進行加鎖處理,再次改造GetSingletone方法。

    public class ObjectClass
    {
        private static ObjectClass singleton = new ObjectClass();
        private static object lockObj = new object();
        private ObjectClass()
        {
        }

        public static ObjectClass GetSingletone()
        {
            lock (lockObj)
            {
                if (singleton == null)
                {
                    singleton = new ObjectClass();
                }
            }
            return singleton;
        }
    }

 加鎖對性能有必定的損耗,若是你的系統對性能要求比較高,咱們對於加鎖的處理還有一種優化方式:雙重檢查加鎖

     public static ObjectClass GetSingletone()
        {
            if (singleton == null)
            {
                lock (lockObj)
                {
                    if (singleton == null)
                    {
                        singleton = new ObjectClass();
                    }
                } 
            }
            return singleton;
        }

使用雙重檢查加鎖,則多線程在運行的時候若是已經建立了單例對象後就不會再進入到lock代碼段以此減小鎖帶來的性能損耗。

而後咱們再來測試一波,啓用50個線程,能夠看到輸出的HashCode是一致的。

四、總結

回到咱們開始講的爲何不用全局變量或者約定來解決單例問題,由於對於咱們開發來講雖然有約定可是咱們不能保證每一個人都按照約定或者濫用全局變量形成問題。

而使用單例模式能進行更好的自我約定和管理,固然咱們也有可能會濫用單例模式,這就須要對它能解決什麼問題如何使用深刻理解。

設計模式並非要生搬硬套,而是在須要的時候符合的場景進行合理使用。

雖然單例模式比較簡單,但經過分析咱們看到問題也很多,要更好的使用須要咱們更好的分析,也但願這篇博文對你有些幫助。

相關文章
相關標籤/搜索