【單例模式】Singleton pattern

前言:有很多時候,在一個生命週期中我們只要一個對象就可以了,比如:線程池,緩存,對話框,日誌,顯卡驅動等等。如果造出多個實例,就會導致許多問題產生,例如:程序的行爲異常、資源使用過量,或者說不一致的結果。

public class Singleton
    {
        private static Singleton instance;
        private Object _synchronizedObj = new Object();

        //private constructor
        private Singleton() { }
       

        public Singleton GetInstance
        {
            get
            {
                if (instance == null)
                    lock (_synchronizedObj)
                    {
                        if (instance == null)
                            instance = new Singleton();
                    }
                return instance;
            }
        }
    }
View Code

 

這裏用到了Lock, 我看過大部分代理都沒有使用到這裏,但是如果不用上,在有多線程的項目中就會出現問題。

我用了兩次NULL的判斷,最外層說因爲如果你第一次創建好了就不需要去進行LOCK,這裏會有消耗。裏面那一個NULL判斷是因爲如果你有多個線程一起來創建,都通過第一層NULL限制,進入了LOCK等待,當第一個對象創建完,放開了LOCK,第二進去以後,如果不加判斷又會創建一次。

 

有人和我說這樣會不會更優雅,因爲不用一個成員變量Object.

 1  public class Singleton
 2     {
 3         private static Singleton instance;
 4 
 5         //private constructor
 6         private Singleton() { }
 7 
 8 
 9         public Singleton GetInstance
10         {
11             get
12             {
13                 if (instance == null)
14                     lock (typeof(Singleton)) //here
15                     {
16                         if (instance == null)
17                             instance = new Singleton();
18                     }
19                 return instance;
20             }
21         }
22     }
View Code


這裏不得不提及 同步索引塊(Sync block index),這個東西被關注的特別少,既然說學習CLR,這個東西可是叱吒風雲(不過大部分的時候,你不會用而已啦).

Sync block index 初始化和過程

  

當CLR初始化的時候,CLR會初始化一個SyncBlock的數組,當一個線程到達Monitor.Enter方法時,該線程會檢查該方法接受的參數的同步塊索引,默認情況下對象的同步塊索引是一 個負數
(實際上並不是負數,我這裏只是爲了敘說方便),那麼表明該對象並沒有一個關聯的同步塊,CLR就會在全局的SyncBlock數組裏找到一個空閒的項,然後將數組的索引賦值給該對象的同步塊索
引,當Monitor.Enter執行時,它會設置SyncBlock裏的內容,標識出已經有一個線程佔用了,當另外一個線 程進入時,它就會檢查SyncBlock的內容,發現已經有一個線程佔用了,該線程就
會等待,當Monitor.Exit執行時,佔用的線程就會釋放SyncBlock,其他的線程可以進入操作了。

注:參考博客園另一篇文章:http://www.cnblogs.com/qianyz/archive/2011/10/26/2224925.html(若要了解更多的SyncBlockIndex的用途和原理,可以看這篇)

 所以對於上面的代碼:

  typeof(Singleton)返回一個Type對象,如果用這個,會返回相同TYPE對象。我們在這裏用lock(typeof(Singleton))鎖定了,相當於鎖定返回的TYPE對象。OK,當我們在其他在用lock(typeof(Singleton))的時候,又鎖了這個對象...2個地方鎖了用了同一個對象,結果不該等待的地方也在等待了。 所以我們這裏用了私有的Object對象來充當關聯進索引塊的對象,無論外面這麼鎖,都鎖不住者裏東西。

  

最近逛博客的時候發現一篇文章說完美單例的,我不是很清楚完美不完美,不過的確要比我認知的單例性能要好寫,附上代碼:

  public class Singleton
    {
        private Singleton()
        {
        }

        private static class SingletonFactory
        {
            public static readonly Singleton Instance;

            static SingletonFactory()
            {
                Instance = new Singleton();
            }
        }

        public static Singleton GetInstance()
        {
            return SingletonFactory.Instance;
        }
    }
View Code


那邊文章說的說Java的單例,談到了JVM的加載機制(JVM保證了類加載的時候是互斥的)如果CLR呢?靜態類在加載包含該類的程序或命名空間時由 .NET Framework 公共語言運行庫 (CLR;特指:C#語言) 自動加載,由於靜態類裏面不能有實例構造(這個很靠譜),我們造了一個靜態構造(相對於java的static{})來初始化對象,因爲在類加載的時候就會初始化這個對象(類加載是互斥的),所以我們就不用鎖對象了,直接調用就可以。

    好處:這個相當於以前的版本,減少了判斷和鎖的機制,而且這裏爲什麼會用靜態類來做初始化工作,首先這個單例的類可能被繼承,方法可能被重寫,還有可能要繼承一些接口,所以用內部靜態類來做的話(靜態類不能實現接口,不能被繼承)功能非常的單利,就算Singlton被繼承了,方法被重寫,無論什麼機制,我們初始化單例的地方已經得到控制,外部是不能隨心所欲的去改變創建單例的模式,只能做一些自己要的修飾,然後再調用內部靜態類獲取單例子,這也可以說是一層封裝。

 

我和同事討論一下這個模式,它固有自己的好處,但是缺點也很明顯,我構建一個Connection對象的時候,我Disponse了,這樣的話我們並不能在獲得一次對象(如果獲取對象
的時候出錯了,也無法再獲取一次,要寫主動加載,這樣並沒有封裝好),但是這個東西如果要構建大對象的話,封裝的還是不錯,還要考慮一下大對象的資源問題(因爲是
static的,GC可不管你), 我一直認爲,寫模式,要寫成我不知道這個是什麼模式,但是用在我這個場景是最合適的,這樣模式就貫通了,而不是生搬硬套...

 

 

 

轉載於:https://www.cnblogs.com/guochenkai/p/3880520.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章