單例模式對於我們來說一點也不模式,是一個常見的名稱,單例模式在程式中的實際效果就是:確保一個程式中只有一個實例,並提供一個全局訪問點,節省系統資源; 單例模式無論是在實際開發中還是在軟體應用中比較常見,比如,windows系統的任務管理器、IIS的HttpApplication、實際項目中的日誌組... ...
c#設計模式之單例模式
場景描述
單例模式對於我們來說一點也不模式,是一個常見的名稱,單例模式在程式中的實際效果就是:確保一個程式中只有一個實例,並提供一個全局訪問點,節省系統資源
單例模式無論是在實際開發中還是在軟體應用中比較常見,比如,windows系統的任務管理器、IIS的HttpApplication、實際項目中的日誌組件等等
實現方式
單例模式為了實現一個實例,那麼只有不把實例創建暴露出去,只通過類本身來創建實例,為了實現效果,需要定義一個私有構造函數
單例模式實現方式有:餓漢式、懶漢式、雙重驗證式、靜態內部類、延遲載入(Lazy)
下麵分別對每一種實現方式做一個簡單的實例,以及其優缺點
餓漢式
/// <summary> /// 創建一個 Singleton 類(餓漢式) /// 這種方式比較常用,但容易產生垃圾對象。 ///優點:沒有加鎖,執行效率會提高。 ///缺點:類載入時就初始化,浪費記憶體。 ///它基於 classloder 機制避免了多線程的同步問題,不過,instance 在類裝載時就實例化, ///雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance 方法, ///但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。 /// </summary> public class SingleObject { //創建 SingleObject 的一個對象 private static SingleObject instance = new SingleObject(); //讓構造函數為 private,這樣該類就不會被實例化 private SingleObject() { Console.WriteLine("我被創建了.餓漢式"); } //獲取唯一可用的對象 public static SingleObject GetInstance() { return instance; } public void ShowMessage() { Console.WriteLine("Hello World.餓漢式"); } }
懶漢式
/// <summary> /// 創建一個 Singleton 類(懶漢式) /// 這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。 /// 優點:第一次調用才初始化,避免記憶體浪費。 /// 缺點:懶漢式在單個線程中沒有問題,但多個線程同事訪問的時候就可能同事創建多個實例,而且這多個實例不是同一個對象。 /// </summary> public class SingleObject1 { //創建 SingleObject 的一個對象 private static SingleObject1 instance; //讓構造函數為 private,這樣該類就不會被實例化 private SingleObject1() { } //獲取唯一可用的對象 public static SingleObject1 GetInstance() { if (instance == null) { instance = new SingleObject1(); Console.WriteLine("我被創建了.懶漢式"); } return instance; } public void ShowMessage() { Console.WriteLine("Hello World.懶漢式"); } }
雙重驗證式
/// <summary> /// 創建一個 Singleton 類(雙重驗證) /// 這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。 /// 優點:第一次調用才初始化,避免記憶體浪費,線程安全。 /// 缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。 /// </summary> public class SingleObject2 { //創建 SingleObject 的一個對象 private static SingleObject2 instance; // 定義一個標識確保線程同步 private static readonly object locker = new object(); //讓構造函數為 private,這樣該類就不會被實例化 private SingleObject2() { } //獲取唯一可用的對象 public static SingleObject2 GetInstance() { //// 如果為空,那麼就加鎖,創建實例 if (instance == null) { lock (locker) { //// 枷鎖成功後,在做一次非空判斷,避免在加鎖期間以創建了實例而導致重覆創建 if (instance == null) { instance = new SingleObject2(); Console.WriteLine("我被創建了.雙重驗證"); } } } return instance; } public void ShowMessage() { Console.WriteLine("Hello World.雙重驗證"); } }
靜態內部類
/// <summary> /// 創建一個 Singleton 類(靜態內部類) /// 這種方式不用加鎖,在效率上和記憶體使用上都比較優秀 /// 剋服了餓漢模式的不足餓漢模式執行效率高,由於在類載入的時候初始化導致記憶體浪費 /// </summary> public class SingletonStatic { /// <summary> /// 內部類 /// </summary> public class SingletonStaticInner { /// <summary> /// 當一個類有靜態構造函數時,它的靜態成員變數不會被beforefieldinit修飾 /// 就會確保在被引用的時候才會實例化,而不是程式啟動的時候實例化 /// </summary> static SingletonStaticInner() { } /// <summary> /// 實例化 /// </summary> internal static SingletonStatic singletonStatic = new SingletonStatic(); } /// <summary> /// 私有構造函數 /// </summary> private SingletonStatic() { Console.WriteLine("我被創建了.靜態內部類"); } /// <summary> /// 獲取實例 /// </summary> /// <returns></returns> public static SingletonStatic GetInstance() { return SingletonStaticInner.singletonStatic; } public void ShowMessage() { Console.WriteLine("Hello World.靜態內部類"); } }
延遲載入(Lazy)
/// <summary> /// 創建一個 Singleton 類(Lazy) /// 該方式是需要.netformwork4+ /// </summary> public class SingletonLazy { private static Lazy<SingletonLazy> singletonLazy = new Lazy<SingletonLazy>(()=>new SingletonLazy()); /// <summary> /// 私有構造函數 /// </summary> private SingletonLazy() { Console.WriteLine("我被創建了.Lazy"); } /// <summary> /// 獲取實例 /// </summary> /// <returns></returns> public static SingletonLazy GetInstance() { return singletonLazy.Value; } public void ShowMessage() { Console.WriteLine("Hello World.Lazy"); } }
每一種創建方式測試
創建一個控制台程式,通過多線程對每一種實現方式使用,查看其實例次數分析:
/* 介紹 意圖:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。 主要解決:一個全局使用的類頻繁地創建與銷毀。 何時使用:當您想控制實例數目,節省系統資源的時候。 如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則創建。 關鍵代碼:構造函數是私有的。 應用實例: 典型的已有應用: 1、windows的任務管理器等 2、IIS的HttpApplication,所有的HttpModule都共用一個HttpApplication實例 在項目中的實際使用場景: 1、日誌組件 2、多線程線程池管理 3、網站計數器 4、配置文件管理 */ class Program { static void Main(string[] args) { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); //// 測試--餓漢式 for (int i = 0; i < 5; i++) { taskList.Add(taskFactory.StartNew(() => { SingleObject.GetInstance(); })); } //// 測試--懶漢式 for (int i = 0; i < 5; i++) { taskList.Add(taskFactory.StartNew(() => { SingleObject1.GetInstance(); })); } //// 測試--雙重驗證 for (int i = 0; i < 5; i++) { taskList.Add(taskFactory.StartNew(() => { SingleObject2.GetInstance(); })); } //// 測試--靜態內部類 for (int i = 0; i < 5; i++) { taskList.Add(taskFactory.StartNew(() => { SingletonStatic.GetInstance(); })); } //// 測試--Lazy for (int i = 0; i < 5; i++) { taskList.Add(taskFactory.StartNew(() => { SingletonLazy.GetInstance(); })); } Console.ReadLine(); } }
運行結果:
總結
根據單例模式是每一種實現方式對比分析,在實際使用過程中:建議採用延遲載入(Lazy)
當然,還有其他類似的實現單例的方式,沒有寫到的,也歡迎大家一起交流,勿噴