一般咱們在寫程序的時候會碰到一個類只容許在整個系統中只存在一個實例(Instance) 的狀況, 好比說咱們想作一計數器,統計某些接口調用的次數,一般咱們的數據庫鏈接也是隻指望有一個實例。Windows系統的系統任務管理器也是始終只有一個,若是你打開了windows管理器,你再想打開一個那麼他仍是同一個界面(同一個實例), 還有好比 作.Net平臺的人都知道,AppDomain 對象,一個系統中也只有一個,全部的類庫都會加載到AppDomain中去運行。只須要一個實例對象的場景,隨處可見,那麼有麼有什麼好的解決方法來應對呢? 有的,那就是 單例模式。數據庫
單例模式(Singleton Pattern):確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。單例模式是一種對象建立型模式。windows
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton GetInstance() { if(instance==null) { instance=new Singleton(); } return instance; } }
客戶端調用代碼:安全
static void Main(string[] args) { Singleton singleto = Singleton.GetInstance(); }
在C#中常常將統一訪問點暴露出一個只讀的屬性供客戶端程序使用,這樣代碼就變成了這樣:網絡
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton GetInstance { get { if (instance == null) { instance = new Singleton(); } return instance; } } }
客戶端調用:多線程
static void Main(string[] args) { Singleton singleton = Singleton.GetInstance; }
假如咱們要作一個程序計數器,一旦程序啓動不管多少個客戶端調用這個 計數器計數的結果始終都是在前一個的基礎上加1,那麼這個計數器類就能夠設計成一個單例模式的類。併發
public class SingletonCounter { private static SingletonCounter instance; private static int number=0; private SingletonCounter() { } public static SingletonCounter Instance { get { if (instance == null) instance = new SingletonCounter(); number++; return instance; } } public int GetCounter(){ return number; } }
客戶端調用:函數
static void Main(string[] args) { //App A call the counter; SingletonCounter singletonA = SingletonCounter.Instance; int numberA = singletonA.GetCounter(); Console.WriteLine("App A call the counter get number was:" + numberA); //App B call the counter; SingletonCounter singletonB = SingletonCounter.Instance; int numberB = singletonA.GetCounter(); Console.WriteLine("App B call the counter get number was:" + numberB); Console.ReadKey(); }
輸出結果:高併發
這個實現是線程不安全的,若是有多個線程同時調用,而且又偏偏在計數器初始化的瞬間多個線程同時檢測到了 instance==null爲true狀況,會怎樣呢?這就是下面要討論的 「加鎖懶漢模式」性能
多個線程同時調用而且同時檢測到 instance == null 爲 true的狀況,那後果就是會出現多個實例了,那麼就沒法保證惟一實例了,解決這個問題就是增長一個對象鎖來確保在建立的過程當中只有一個實例。(鎖能夠確保鎖住的代碼塊是線程獨佔訪問的,若是一個線程佔有了這個鎖,其它線程只能等待該線程釋放鎖之後才能繼續訪問)。spa
public class SingletonCounter { private static SingletonCounter instance; private static readonly object locker = new object(); private static int number = 0; private SingletonCounter() { } public static SingletonCounter Instance { get { lock (locker) { if (instance == null) instance = new SingletonCounter(); number++; return instance; } } } public int GetCounter() { return number; } }
客戶端調用代碼:
static void Main(string[] args) { for (int i = 1; i < 100; i++) { var task = new Task(() => { SingletonCounter singleton = SingletonCounter.Instance; int number = singleton.GetCounter(); Console.WriteLine("App call the counter get number was:" + number); }); task.Start(); } Console.ReadKey(); }
輸出結果:
這種模式是線程安全,即便在多線程的狀況下仍然能夠保持單個實例。那麼這種模式會不會有什麼問題呢?假如系統的訪問量很是大,併發很是高,那麼計數器就會是一個性能瓶頸,由於對鎖會使其它的線程沒法訪問。在訪問量不大,併發量不高的系統尚可應付,若是高訪問量,高併發的狀況下這樣作確定是不行的,那麼有什麼辦法改進呢?這就是下面要討論的「雙檢查加鎖懶漢模式」。
加鎖懶漢模式雖然保證了系統的線程安全,可是卻爲系統帶來了新能問題,主要的性能來自鎖帶來開銷,雙檢查就是解決這個鎖帶來的問題,在鎖以前再作一次 instance==null的檢查,若是返回true就直接返回 單例對象了,避開了無謂的鎖, 咱們來看下,雙檢查懶漢模式代碼:
public class DoubleCheckLockSingletonCounter { private static DoubleCheckLockSingletonCounter instance; private static readonly object locker = new object(); private static int number = 0; private DoubleCheckLockSingletonCounter() { } public static DoubleCheckLockSingletonCounter Instance { get { if (instance == null) { lock (locker) { if (instance == null) { instance = new DoubleCheckLockSingletonCounter(); } } } number++; return instance; } } public int GetCounter() { return number; } }
客戶端調用代碼和「懶漢加鎖模式」相同,輸出結果也相同。
單例模式除了咱們上面講的三種懶漢模式外,還有一種叫「餓漢模式」的實現方式,「餓漢模式」直接在Singleton類裏實例化了當前類的實例,而且保存在一個靜態對象中,由於是靜態對象,因此在程序啓動的時候就已經實例化好了,後面直接使用,所以不存在線程安全的問題。
下面是「餓漢模式」的代碼實現:
public class EagerSingletonCounter { private static EagerSingletonCounter instance = new EagerSingletonCounter(); private static int number = 0; private EagerSingletonCounter() { } public static EagerSingletonCounter Instance { get { number++; return instance; } } public int GetCounter() { return number; } }
單例模式只有一個角色很是簡單,使用的場景也很明確,就是一個類只須要、且只能須要一個實例的時候使用單例模式。
」餓漢模式「在程序啓動的時候就已經實例化好了,而且一直駐留在系統中,客戶程序調用很是快,由於它是靜態變量,雖然完美的保證線程的安全,可是若是建立對象的過程很複雜,要佔領系統或者網絡的一些昂貴的資源,可是在系統中使用的頻率又極低,甚至系統運行起來後都不會去使用該功能,那麼這樣一來,啓動以後就一直佔領着系統的資源不釋放,這有些得不償失。
「懶漢模式「 剛好解決了」餓漢模式「這種佔用資源的問題,」懶漢模式」將類的實例化延遲到了運行時,在使用時的第一次調用時才建立出來並一直駐留在系統中,可是爲了解決線程安全問題, 使用對象鎖也是 影響了系統的性能。這兩種模式各有各的好處,可是又各有其缺點。
有沒有一種折中的方法既能夠避免一開始就實例化且一直佔領系統資源,又沒有性能問題的Singleton呢? 答案是:有的。
「餓漢模式「類不能實現延遲加載,無論用不用始終佔據內存;」懶漢式模式「類線程安全控制煩瑣,並且性能受影響。咱們用一種折中的方法來解決這個問題,針對主要矛盾, 即:既能夠延時加載又不影響性能。
在Singleton的內部建立一個私有的靜態類用於充當單例類的」初始化器「,專門用來建立Singleton的實例:
public class BestPracticeSingletonCounter { private static class SingletonInitializer{ public static BestPracticeSingletonCounter instance = new BestPracticeSingletonCounter(); } private static int number = 0; private BestPracticeSingletonCounter() { } public static BestPracticeSingletonCounter Instance { get { number++; return SingletonInitializer.instance; } } public int GetCounter() { return number; } }
這種模式兼具了」餓漢「和」懶漢「模式的優勢有摒棄了其缺點,能夠說是一個完美的實現。