C# 基礎(十四)C#單例模式:首先介紹 單執行緒、多執行緒、加鎖 單例模式。然後介紹單例模式的執行緒同步:多執行緒有序訪問共享記憶體。
一、簡介
本篇文章將介紹如何使用單例模式,也就是類的例項化,在整個專案的生命週期內,只例項化一次。在單例模式中,往往可以看到如SourceCode.cs:這樣的結構的。
SourceCode.cs:
public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
我的專案中,也有類似的結構:
好了,我把 private GlobalDataStruct()的程式碼顯示如下:
private GlobalDataStruct() { m_cApplicationDir = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase; m_dbKrayConfig = new cKrayDBase(); m_dbPatientInfo = new cKrayDBase(); m_csStudyPatientInfo = new PatientInfo(); m_csStudySeriesInfo = new SeriesInfo(); m_csConfigFileInfo = new ConfigStructData(); m_bEnableExpFlag = true; m_bConfigDbOpenFlag = false; m_bPatientDbOpenFlag = false; m_bHandPressStatus = false; m_bFootBreakStatus = false; m_bGenLoadStatus = false; m_bCanLoadStatus = false; m_bAlreadSelectCheckListStatus = false; m_bReviewCheckFlag = false; m_bHandCheckFlag = false; m_bSpliceFlag = false; m_bDRGenExpStatus = false; m_bFluGenExpStatus = false; m_bLoadCurCheckDeviceMode = true; m_sStudyBodyName = ""; m_nFrameRate = 0; m_nGrabAcqStatus = 0; m_nSelectCheckIndex = 0; m_strDBDriver = "Provider = Microsoft.ACE.OLEDB.12.0; Data Source ="; InitPublicInfo(); InitDeviceInfo(); }
並且,我把 public static GlobalDataStruct CreateInstance()的程式碼顯示如下:
public static GlobalDataStruct CreateInstance() { //雙重加鎖 if (_instance == null) { lock (lockobj)//執行緒同步問題 { if (_instance == null) { _instance = new GlobalDataStruct(); } } } return _instance; }
好了不用多說,你會發現SourceCode.cs和我截圖的程式碼結構,有著異曲同工之妙。並且發現,下面的幾部分基本相同
private static Singleton instance
private Singleton() { }
public static Singleton getInstance() {}
好了,下面,我先來分析這三部分的內容,然後再分析單例模式的寫法。
二、單例模式的結構
主要參考(這三個連結的文章都很好):
https://www.cnblogs.com/zhili/p/SingletonPatterm.html
https://zhidao.baidu.com/question/547674677.html
https://blog.csdn.net/u012975370/article/details/62046317
單例模式的結構,往往如下:
public class Singleton {
//類的例項化:必須是private,防止Singleton類例項化時對它進行更改。且用static保留一份。
private static Singleton instance;
//預設建構函式,但是外界不能建立該類例項
private Singleton() {
}
//public給外部訪問。且用static保留一份。
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
private static Singleton instance;——表示類的例項化,用static來例項化。
private Singleton() { }—— 一般來說,C#類的預設建構函式,都是public的。但是這裡卻用到了private。原因很簡單,防止使用者使用建構函式重新構造,確保生成的例項是單一的。比如我再次例項,就不行啦,如下圖:
public static Singleton getInstance() {} ——定義公有方法提供一個全域性訪問點。
好了,單例模式的結構,我們已經基本弄明白了。接下來,看看單例模式的幾種常用寫法。
三、單例模式的幾種寫法
主要參考:
https://www.cnblogs.com/zhili/p/SingletonPatterm.html提到了單執行緒、多執行緒、加鎖
https://blog.csdn.net/u012975370/article/details/62046317提到了懶漢式、餓漢式、加鎖、列舉、靜態內部類、容器
1、單例模式(懶漢式,),在單執行緒下的實現方法:
/// <summary>
/// 單例模式的實現
/// </summary>
public class Singleton
{
// 定義一個靜態變數來儲存類的例項
private static Singleton uniqueInstance;
// 定義私有建構函式,使外界不能建立該類例項
private Singleton()
{
}
/// <summary>
/// 定義公有方法提供一個全域性訪問點,同時你也可以定義公有屬性來提供全域性訪問點
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 如果類的例項不存在則建立,否則直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
2、單例模式,在多執行緒下的實現方法:
方法1:簡單的加鎖
/// <summary>
/// 單例模式的實現
/// </summary>
public class Singleton
{
// 定義一個靜態變數來儲存類的例項
private static Singleton uniqueInstance;
// 定義一個標識確保執行緒同步
private static readonly object locker = new object();
// 定義私有建構函式,使外界不能建立該類例項
private Singleton()
{
}
/// <summary>
/// 定義公有方法提供一個全域性訪問點,同時你也可以定義公有屬性來提供全域性訪問點
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 當第一個執行緒執行到這裡時,此時會對locker物件 "加鎖",
// 當第二個執行緒執行該方法時,首先檢測到locker物件為"加鎖"狀態,該執行緒就會掛起等待第一個執行緒解鎖
// lock語句執行完之後(即執行緒執行完之後)會對該物件"解鎖"
lock (locker)
{
// 如果類的例項不存在則建立,否則直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
方法2:雙重加鎖方式
與方法不同的是,雙重加鎖效率更高,只需要新增 if (uniqueInstance == null) {} 這行即可。
/// <summary>
/// 單例模式的實現
/// </summary>
public class Singleton
{
// 定義一個靜態變數來儲存類的例項
private volatile static Singleton uniqueInstance;
// 定義一個標識確保執行緒同步
private static readonly object locker = new object();
// 定義私有建構函式,使外界不能建立該類例項
private Singleton()
{
}
/// <summary>
/// 定義公有方法提供一個全域性訪問點,同時你也可以定義公有屬性來提供全域性訪問點
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 當第一個執行緒執行到這裡時,此時會對locker物件 "加鎖",
// 當第二個執行緒執行該方法時,首先檢測到locker物件為"加鎖"狀態,該執行緒就會掛起等待第一個執行緒解鎖
// lock語句執行完之後(即執行緒執行完之後)會對該物件"解鎖"
// 雙重鎖定只需要一句判斷就可以了
if (uniqueInstance == null)
{
lock (locker)
{
// 如果類的例項不存在則建立,否則直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
好了,你可能會注意到,雙重加鎖程式碼中,多了volatile 關鍵字,接下來分析volatile關鍵字。volatile關鍵字與共享記憶體有關。
四、共享記憶體(volatile關鍵字)、執行緒同步
執行緒同步:就是要保證多個執行緒 安全地 訪問共享記憶體。
主要參考:
https://blog.csdn.net/u012975370/article/details/62046317
https://blog.csdn.net/lonestar555/article/details/49025169
多個執行緒同時訪問一個變數,CLR為了效率,允許每個執行緒進行本地快取,這就導致了變數的不一致性。volatile就是為了解決這個問題,volatile修飾的變數,不允許執行緒進行本地快取,每個執行緒的讀寫都是直接操作在共享記憶體上,這就保證了變數始終具有一致性。即共享記憶體允許多個執行緒有序地讀寫變數,這些變數儲存在共享記憶體上,保證了變數的一致性(比如單例項類)。
public class Singleton {
private volatile static Singleton instance = null;
public static Singleton getInstance() {
Singleton inst = instance; // 建立臨時變數
if (inst == null) {
synchronized (Singleton.class) {
inst = instance;
if (inst == null) {
inst = new Singleton();
}
}
}
return inst; // 返回臨時變數
}
private Singleton() {}
}
五、總結
執行緒同步問題,雖然可以保證安全地訪問共享記憶體,但是效率較慢。