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


單例模式是所有設計模式中最簡單的模式,也是我們平常經常用到的,單例模式通常被我們應用於線程池、緩存操作、隊列操作等等。

單例模式旨在創建一個類的實例,創建一個類的實例我們用全局靜態變量或者約定也能辦到單例的作用,為什么我們要用單例模式?

接下來我們就從如何形成單例模式,單例模式創建的過程來講解。

1、單例如何形成

我們平常創建一個對象需要new對象,假如有一個對象ObjectClass我們實例化它。

new ObjectClass()

如果另外一個類要使用ObjectClass則可以再通過new來創建另外一個實例化,如果這個類是public 則我們可以在使用的時候多次實例化對象。

那我們怎么保證類不被其他類實例化,利用private關鍵字我們可以采用私有構造函數來阻止外部實例化該類。

public class ObjectClass
{
   private ObjectClass()
    {
    }    
}

這樣一來我們無法實例化ObjectClass則我們就無法使用它。那我們要怎么實例化呢?

由於私有構造方法我們只能在內部訪問,所以我們可以用一個內部方法實例化ObjectClass,為了外部能夠訪問這個方法我們將這個方法設置成static。

這樣做了之后確保返回對象始終是第一次創建的對象,我們用一個私有靜態對象來存儲實例化的對象,如果對象沒創建我們則立即創建,如果已經創建就返回已經創建的對象。

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

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

到這里我們的單例模式就形成了,單例模式定義:

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

2、多線程導致單例模式問題

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

    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變量還沒被實例化則可能有多個線程進入到實例化代碼,以至於返回的實例化對象不是同一個。

3、解決多線程單例問題

由於多線程導致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是一致的。

4、總結

回到我們開始講的為什么不用全局變量或者約定來解決單例問題,因為對於我們開發來說雖然有約定但是我們不能保證每個人都按照約定或者濫用全局變量造成問題。

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

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

雖然單例模式比較簡單,但通過分析我們看到問題也不少,要更好的使用需要我們更好的分析,也希望這篇博文對你有些幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM