1.簡介

上圖中在點擊選單按鈕后不斷的彈出子表單,顯然這種方式是不合理的,此場景正是可以運用單例模式來解決的一種運用,
其核心本質就是讓類的物件只有一個,使用到的地方還包括:執行緒池、快取、對話框等,
單例模式其實很好理解,最核心的含義就是通過該設計模式來確保一個類只有一個實體,并對外提供一個全域訪問的方式,
該模式最關鍵點就是“確保”,如何真正的確保一個類只有一個實體,理解單例的程序就是如何懂得實作真正意義上的“確保”,
2.單例模式的“三板斧”
2.1拒絕對外
設想下如果阿拉神燈允許你可以許一個愿望成真,這樣的機會你會給別人嗎?此場景同樣可以隱喻到我們當下講述的單例模式,
單例模式目前需求是一個類只能實體化一個物件,那么這樣的機會你會給外人嗎?此場景引入了實作單例模式的第一點——拒絕對外,
這里的拒絕對外說的是類創建物件的建構式不允許外部進行呼叫,
如何在代碼上實作呢?單例模式的一個關鍵點之一,建構式私有化,這也印證了上述中說到的如果有件事情你只能做一次那么你肯定會留給自己,
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 }View Code
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/4535.html
標籤:設計模式
上一篇:高并發實時性網路視頻監控專案實戰
下一篇:備忘錄模式
