單例模式的五個實現方法比較 via C#

單例模式:就是指一個類裏面只一個實例,並提供一個全局的訪問點。在C#裏,單例模式的實現方便有很多種。個人的見解是,由於整個類只有一個實例對象,因此,必須做到這人實例對象只能由類它本身來管理,即由該類來創建與銷燬這個實例。

以下是五種創建單例的做法。雖然它們表面上都實現了由類本身管理這個實例,但是卻未必都是正確的做法。

方法一:
 public class Singleton
    {
        private static Singleton _instance;

        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
    說明:以上的實現在單線程的情況上基本正確,但是卻幾乎不可能在多線程的情況下正確運行。其原因是“紅色”部分的幾行語句並不是一個原子操作,僅從C#而言,就已經是由多個語句組成的;如果再變成IL代碼或者是底層的彙編代碼,那將是更多條語句。因此,這裏無法實現在“紅色”部分代碼的原子操作。所以在多線程的訪問下有可能會失敗。

方法二:
基於”方法一“,爲了確保判斷實例與創建實例的“原子操作”,那最簡單的做法就是加鎖。
 public class Singleton
    {
        private static Singleton _instance;

        private static object obj = new object();
        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {

            lock (obj)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
            return _instance;
        }
    }
    說明:看起來”方法二“已經修復了”方法一“的bug了。但是,難道每一次調用:“GetInstance()"方法是我都不得不加鎖嗎?要知道,鎖可不是說加就加的,是有代價的。

方法三:
繼續改進”方法二“吧。我們的目標是不要每一次請求”GetInstance()“就加鎖,其實,加鎖只有在_instance未實例話時需要,所以:
 public class Singleton
    {
        private static Singleton _instance;

        private static object obj = new object();
        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            if (_instance == null)
            {
                lock (obj)
                {
                    if (_instance == null)
                    {
                        _instance = new Singleton();
                    }
                }
            }
            return _instance;
        }
    }
    說明:經過了”方法三“的處理之後,”紅色“部分的代碼只會完整地執行一次,而以後的請求將不會再加鎖了。因此,加鎖的次數將會大大減少,成功的加鎖只會有一次。

方法四:
    爲什麼一定要加鎖呢,不如:
 public class Singleton
    {
        private static Singleton _instance = new Singleton();

        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            return _instance;
        }
    }
    說明:充分利用類的靜態構造函數吧。靜態字段是由類的靜態構造函數負責實例化。在多線程的情況下,CLR確保了每個AppDomain中只對靜態構造函數執行一次。在多線程的環境下,CLR爲每個想要調用靜態構造函數的方法提供一個互拆線程同步鎖。因此,只有一個線程可以獲得這個鎖,並執行靜態構造函數對字段進行實例化。其他的線程處於阻塞的狀態。等第一個線程釋放鎖時,其他線程被喚醒,但是發現構造器的代碼已經執行過了,因此,不再繼續執行,而是直接返回。

方法五:
    但是,這樣一來就不是”按需分配“了,如果這個Singleton實例的初始化需要大量的時間和資源呢,而且又無法保證這個Singleton實例一定會被進程使用。這樣不就是浪費資源嗎?
  public class Singleton
    {
        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            return Nest.Instance;   
        }

        class Nest
        {
            private static Singleton _instance = new Singleton();

            private Nest()
            {
            }

            public static Singleton Instance
            {
                get { return _instance; }
            }
        }
    }

說明:
    和”方法四“類似,我們可以保證這個單例模式在多線程的情況下是可正確運行的,因爲Nest 類只有Singleton可訪問,並且Singleton類是不能被繼承的,因爲它的構造方法爲private。而且我們也做到了”按需分配“。
    如果Singleton.GetInstance()方法沒有被調用,也就是沒有發起對Nest的靜態構造函數的調用,因此,_instance實例不會被創建。
    只有在調用Singleton.GetInstance()方法被調用之後,纔會調用Nest類的靜態構造函數(而且這個靜態構造函數的調用 對多線程也是需要加互斥同步鎖的),所以可以確保只對_instance實例創建一次。


各位看官有何想法?


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