定義
百度文庫:單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。
標准定義:Singleton保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
UML圖
示例
(1.1)定義一個私有的靜態的全局變量
public class Singleton { //定義一個私有的靜態的全局變量 private static Singleton singleton; }
(1.2)設置私有構造函數
public class Singleton { //定義一個私有的靜態的全局變量 private static Singleton singleton; /// <summary> /// 設置私有構造函數(外部無法使用new創建實例) /// </summary> private Singleton() { } }
(1.3)設置一個全局訪問點,靜態方法供外部調用
public class Singleton { //定義一個私有的靜態的全局變量 private static Singleton singleton; /// <summary> /// 設置私有構造函數(外部無法使用new創建實例) /// </summary> private Singleton() { } /// <summary> /// 設置一個全局訪問點,靜態方法供外部調用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { //判斷保證實例化一次 if (singleton == null) singleton = new Singleton(); return singleton; } }
(1.4)調用代碼
class Program { static void Main(string[] args) { Singleton instanceA = Singleton.GetInstance(); Singleton instanceB = Singleton.GetInstance(); Console.WriteLine(instanceA.Equals(instanceB) ? "一個實例" : "不是一個實例"); Console.ReadKey(); } }
結果顯示一個實例,那么一個初步的單例模式我們就體驗完畢了
優化與思考
剛剛我們屬於單線程,那有沒有這樣一種情況,有多個線程一起創建當前實例,一個線程剛剛進入判斷IF中,另一個也進來了,此時興許有實例化2次的情況,這樣在某種情況下雖然不會報錯,但是默認多次實例化卻偏離了我們單例的初衷,下面我們來大概模擬一下這種情況.
(2.1)修改創建-我們在實例化代碼位置隨意輸出內容判斷進入次數
public class Singleton { //定義一個私有的靜態的全局變量 private static Singleton singleton; /// <summary> /// 設置私有構造函數(外部無法使用new創建實例) /// </summary> private Singleton() { } /// <summary> /// 設置一個全局訪問點,靜態方法供外部調用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { //判斷保證實例化一次 if (singleton == null) { singleton = new Singleton(); Console.WriteLine(DateTime.Now.ToString()); } return singleton; } }
這里我輸出了時間
(2.2)調用多線程這里我們用2個線程舉例
class Program { static void Main(string[] args) { Singleton instance = null; Parallel.For(0, 2, (i) => { instance = Singleton.GetInstance(); }); Console.ReadKey(); } }
(2.3)結果居然出現了2次,也就是實例化2次
分析
為了避免這種情況,我們可以將線程鎖定。意味着鎖定之后每次只會有一個線程進入判斷,實例化對象。一定程度避免了多次實例化。
(3.1)增加只讀靜態對象("鎖"對象)
//只讀靜態對象 private static readonly object syncObject = new object();
(3.2)實例化加入判斷
/// <summary> /// 設置一個全局訪問點,靜態方法供外部調用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { if (singleton == null) { lock (syncObject) { //判斷保證實例化一次 singleton = new Singleton(); Console.WriteLine(DateTime.Now.ToString()); } } return singleton; }
(3.3)結果
???怎么回事,怎么又出現2次了。。#¥¥&&@@@)¥)(@*@
所以最終我們雙重鎖定來解決問題在lock內部再加一層判定
/// <summary> /// 設置一個全局訪問點,靜態方法供外部調用 /// </summary> /// <returns></returns> public static Singleton GetInstance() { if (singleton == null) { lock (syncObject) { //判斷保證實例化一次 if (singleton == null) { singleton = new Singleton(); Console.WriteLine(DateTime.Now.ToString()); } } } return singleton; }
結果:
補充:
我們上邊的例子采用的都是lazy load也就是懶加載,用的時候加載,當然還有一種方式就是餓漢模式,就是類初始化的時候直接實例化實例
/// <summary> /// 餓漢 /// </summary> public sealed class SingletonHungry { private static readonly SingletonHungry singleton = new SingletonHungry(); public static SingletonHungry GetInstance() { return singleton; } }