1.簡介設計模式
上圖中在點擊菜單按鈕後不斷的彈出子窗體,顯然這種方式是不合理的。此場景正是能夠運用單例模式來解決的一種運用。緩存
其核心本質就是讓類的對象只有一個,使用到的地方還包括:線程池、緩存、對話框等。多線程
單例模式其實很好理解,最核心的含義就是經過該設計模式來確保一個類只有一個實例,並對外提供一個全局訪問的方式。併發
該模式最關鍵點就是「確保」,如何真正的確保一個類只有一個實例,理解單例的過程就是如何懂得實現真正意義上的「確保」。ide
2.單例模式的「三板斧」函數
2.1拒絕對外性能
設想下若是阿拉神燈容許你能夠許一個願望成真,這樣的機會你會給別人嗎?此場景一樣能夠隱喻到咱們當下講述的單例模式,spa
單例模式目前需求是一個類只能實例化一個對象,那麼這樣的機會你會給外人嗎?此場景引入了實現單例模式的第一點——拒絕對外,線程
這裏的拒絕對外說的是類建立對象的構造函數不容許外部進行調用。設計
如何在代碼上實現呢?單例模式的一個關鍵點之一,構造函數私有化,這也印證了上述中說到的若是有件事情你只能作一次那麼你確定會留給本身。
public Singleton
{
private Singleton (){}
}
2.2.惟一對象的容器
經過私有構造函數對外關閉了實例化對象的入口,此時類的內部是實例化的惟一途徑。那麼如今須要爲實例對象準備一個存儲的容器來存儲——聲明靜態變量。
public Singleton
{
private Singleton (){ }
private static Singleton uniqueObj=null;
}
2.3建立惟一對象並提供全局訪問點
目前已經實現了構造函數私有化和對象的容器,那麼如今須要實例化一個惟一的對象並提供外部訪問的一種方式。
外部目前已經沒法獲取實例,那麼惟一的方式就是提供一個靜態的函數,外部直接經過類名來調用函數。在函數內部判斷容器變量是否爲空,
爲空表示尚未建立實例,那麼就經過私有構造函數來建立實例賦值到容器變量。在不爲空的狀況下說明惟一的對象已經存在,直接返回容器變量。
單例模式的「三板斧」最終代碼以下:
如此一來就能夠實現一個普通的單例模式,這裏爲何說普通由於這個「三板斧」的組成是一個最常規而且還存在一些瑕疵的單例模式,
後續會講具體的緣由,咱們先驗證下這樣的方式是否能保證類僅有一個實例。如圖:
3.單例模式「三板斧」的瑕疵——多線程併發
上述講的普通的單例模式若是使用了多線程獲取對象實例,那麼會致使調用方法後建立多個實例從而不能保證單個實例的需求。
咱們以構造函數的特色來驗證下這種狀況,如圖:
上圖的驗證中構造函數被執行屢次,那麼表明建立多個實例,從而證實多線程併發的時候沒法保證對象的實例僅有一個。
4.如何解決多線程併發帶來的問題
4.1.線程同步
使用C#關鍵字lock的特性實現線程同步。在併發的時候可能會有多個線程同時進入實例化對象的代碼塊中,
利用線程同步在訪問實例化對象代碼塊時線程造成排隊機制來確保不會同時建立多個實例。改進下代碼來看看效果
如圖:
此方式會有一個問題,實際上在第一次執行此方法時才須要同步。由於一旦設置好了實例對象,就沒有必要進行同步。
而目前的作法是每次使用的時候都會執行同步,而同步次數過多會下降程序的性能,若是不考慮性能的消耗,那麼也能夠選擇此方式。
4.2.靜態變量
直接使用聲明一個靜態字段並對其進行實例化賦值,此作法是利用靜態的原理來作到類僅有一個對象的方式。
咱們能夠回顧分析下靜態成員的特色:
GC不會對靜態成員進行資源回收而且會常駐於程序內存中;
靜態成員使用靜態構造函數初始化,靜態構造函數由CLR執行而且只會執行一次從而保證了靜態變量只會存儲一個實例,另外該方式也解決了多線程併發帶來的問題。
代碼以下:
1 class Singleton 2 { 3 private Singleton() { } 4 private static Singleton uniqueObj = new Singleton(); 5 public static Singleton CreateInstance() 6 { 7 return uniqueObj; 8 } 9 }
4.2.1.「延遲實例化」
該方式是一種快速實現方式,代碼上甚至能夠簡寫:直接將靜態變量改爲公有的,外部直接訪問這個公有的靜態變量來實現單例模式。
該模式一樣存在一個問題:此模式摒棄了「延遲實例化」。在使用lock的方式中,咱們獲取實例的方式是經過一個靜態函數來獲取的,
而目前的方式是直接使用靜態字段。熟悉靜態成員初始化的朋友應該清楚,靜態成員初始化賦值的時候是當有代碼對類型第一次訪問的時候進行的。
咱們試想一下,若是程序中存在一個單例模式,可是在使用某個功能時沒有涉及到單例模式的使用,可是使用的這個功能訪問到了單例模式的類型。
這就致使單例模式的對象沒有使用的需求卻被建立,這就致使了沒必要要的消耗。而lock方式中經過靜態函數來獲取,那麼此方式只要真正有道單例模式的時候纔會建立對應的實例對象。
此方式也有個名詞叫作餓漢式,比如一桌菜沒有上齊,飢餓的大漢就能夠吃起來。
4.3.「雙重檢查加鎖」
此方式是目前最爲完善的方式,經過「雙重檢查加鎖」的方式能夠保證方法執行時只會在第一次的時候進行同步避免了屢次同步的性能消耗,同時也能實現「延遲實例化」。
PS:另外經過程序進行線程調試就能夠看到該方式只在第一次方法時才進行同步。
代碼以下:
1 class Singleton 2 { 3 private Singleton() { Console.WriteLine("每建立一個對象調用一次構造函數"); } 4 private static Singleton uniqueObj = null; 5 private static object obj = new object(); 6 7 public static Singleton CreateInstance() 8 { 9 if (uniqueObj == null) 10 { 11 lock (obj) 12 { 13 if (uniqueObj == null) 14 { 15 uniqueObj = new Singleton(); 16 } 17 } 18 } 19 return uniqueObj; 20 } 21 }
5.Demo源碼
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Data; 5 using System.Data.OleDb; 6 using System.Diagnostics; 7 using System.IO; 8 using System.Linq; 9 using System.Text; 10 using System.Text.RegularExpressions; 11 using System.Threading; 12 13 namespace MyDebug 14 { 15 16 class Singleton 17 { 18 #region MyRegion 19 private Singleton() { Console.WriteLine("每建立一個對象調用一次構造函數"); } 20 private static Singleton uniqueObj = null; 21 private static object obj = new object(); 22 23 public static Singleton CreateInstance() 24 { 25 if (uniqueObj == null) 26 { 27 lock (obj) 28 { 29 if (uniqueObj == null) 30 { 31 uniqueObj = new Singleton(); 32 } 33 } 34 } 35 return uniqueObj; 36 } 37 #endregion 38 39 #region 靜態變量實例化(已註釋) 40 //private Singleton() { } 41 //private static Singleton uniqueObj = new Singleton(); 42 //public static Singleton CreateInstance() 43 //{ 44 // return uniqueObj; 45 //} 46 #endregion 47 48 #region lock同步(已註釋) 49 //private static Singleton uniqueObj = null; 50 //private static object obj = new object(); //lock資源對象 51 //public static Singleton CreateInstance() 52 //{ 53 // lock (obj) 54 // { 55 // if (uniqueObj == null) 56 // { 57 // uniqueObj = new Singleton(); 58 // } 59 // } 60 // return uniqueObj; 61 //} 62 #endregion 63 } 64 65 class Program 66 { 67 static void Main(string[] args) 68 { 69 //多線程 70 for (int i = 0; i < 5; i++) 71 { 72 Thread r1 = new Thread(() => 73 { 74 Singleton B = Singleton.CreateInstance(); 75 }); 76 r1.Start(); 77 } 78 79 Console.ReadKey(); 80 } 81 82 83 84 85 } 86 87 88 89 90 }