標籤:div 第一個 性問題 處理 article 手寫 .net 多線程 加鎖
(新手寫部落格,主要是對自己學習的歸納總結。會對很多小細節詳解。)
單例模式的定義:
確保一個類只有一個執行個體,並提供一個全域訪問點。
首先執行個體大家應該都明白就是類產生對象的過程簡單的就是String s=new String(),則s就是個執行個體。
Q:如何只產生一個執行個體?
A:1)首先必須將建構函式變為私人從而防止其他類執行個體化,並且只能有一個建構函式。因為系統會預設一個無參建構函式,而且預設public存取修飾詞。 所以必須寫一個私人無參讓預設無效。(通常單例模式都是不帶形參的)
2)在該類中聲明一個自己本身的靜態執行個體,然後通過靜態方法返回。
Q:如何提供一個全域訪問點?
A:在類中建立一個公用並且靜態屬性。(因為靜態方法是類中的一個成員方法,屬於整個類,即不用建立任何對象也可以直接調用。單例模式是不允許其他類執行個體的。)
代碼:
分為兩種模式:
1.LAZY模式
就是消極式載入, 設計模式是為了避免一些無謂的效能開銷而提出來的,所謂消極式載入就是當在真正需要資料(讀取屬性值)的時候,才真正執行資料載入操作.有效使用它可以大大提高系統效能。
2.餓漢模式
與LAZY模式相反 ,載入時會將自己執行個體化。起來最容易的單例模式。
分析代碼1:(經典)
1 // 不要用這種方式
2 public sealed class Singleton 3 { 4 private static Singleton instance=null;//聲明自己本身的靜態執行個體 5 private Singleton(){}//私人構造 6 public static Singleton Instance //提供全域訪問點 7 { 8 if (instance==null)//執行個體不存在則建立 9 {10 instance = new Singleton();11 }12 return instance;13 }14 }
該代碼僅供理解,單例模式的定義。
問題:該方法是非安全執行緒的,當有兩個線程同時進入時,如果instance為null則都會建立執行個體。實際上,在測試以前,執行個體就已經有可能被建立了,但是記憶體模型不能保證這個執行個體能被其他的線程看到。
下面我們最佳化改進
分析代碼2:(非安全線程)
public sealed class Singleton{ private static Singleton instance = null; private static readonly object padlock = new object();//定義一個標識確保線程同步 Singleton(){} public static Singleton Instance { lock (padlock)//線程到達時加鎖 運行完之後解鎖 當遇到加鎖線程就會掛起等待解鎖 { if (instance == null) { instance = new Singleton(); } return instance; } }}
以上解決了多線程問題。
問題:效能上來說,鎖變成了每次都必須的當這個執行個體被響應的時候。此時完全沒必要對線程輔助對象加鎖之後再去判斷,所以上面的實現方式增加了額外的開銷。
下面我們進行最佳化改進:
程式碼分析3:(雙重鎖定)
1 public sealed class Singleton 2 { 3 private static Singleton instance = null; 4 private static readonly object padlock = new object(); 5 Singleton(){} 6 public static Singleton Instance 7 { 8 get 9 {10 if (instance == null)//外層的if語句塊,這使得每個線程欲擷取執行個體時不必每次都得加鎖,因為只有執行個體為空白時(即需要建立一個執行個體),才需加鎖建立11 {12 lock (padlock)13 {14 if (instance == null)15 {16 instance = new Singleton();17 }18 }19 }20 return instance;21 }22 }23 }
這種“雙重檢查鎖定”理論上是完美的
問題是:並不能保證它會在單一處理器或多處理器電腦上順利運行。(反正就是有問題吧 之後再研讀一下 看看具體是怎麼回事)
程式碼分析4:(不完全LAZY)
1 public sealed class Singleton 2 { 3 private static readonly Singleton instance = new Singleton(); 4 5 // 顯示的static 建構函式 6 //靜態建構函式抑制了beforefieldinit 特性(訪問成員之前就執行靜態函數) 7 static Singleton(){} 8 private Singleton(){} 9 public static Singleton Instance10 {11 get12 {13 return instance;14 }15 }16 }
不完全LAZY模式(通過抑制beforefildinit特性並不能起到太大的效果)
程式碼分析5:(完全LAZY)
1 public sealed class Singleton 2 { 3 private Singleton(){} 4 public static Singleton Instance { get { return Nested.instance; }} 5 //嵌套類 6 private class Nested 7 { 8 //抑制beforefieldinit特性 9 static Nested(){}10 internal static readonly Singleton instance = new Singleton();11 }12 }
這裡使用了嵌套類(巢狀型別是LAZY載入的,也就是說巢狀型別在使用他時才會初始化)
程式碼分析6:(Lazy<T>)
1 public sealed class Singleton2 {3 //使用.NET4 Lazy<T>4 private static readonly Lazy<Singleton> lazy =new Lazy<Singleton>(() => new Singleton());5 public static Singleton Instance { get { return lazy.Value; } }6 private Singleton() {}7 }
Lazy<T> 對象初始化預設是安全執行緒的,在多線程環境下,第一個訪問 Lazy<T> 對象的 Value 屬性的線程將初始化 Lazy<T> 對象,以後訪問的線程都將使用第一次初始化的資料。
以上全部是LAZY模式,現在瞭解下餓漢模式
程式碼分析7:
public sealed class Singleton{ private static readonly Singleton instance=new Singleton();//直接執行個體化 private Singleton(){} public static Singleton Instance() { return instance; }}
在這種模式下,無需自己解決執行緒安全性問題,CLR會給我們解決。由此可以看到這個類被載入時,會自動執行個體化這個類,而不用在第一次調用Instance()後才執行個體化出唯一的單例對象。
為了最佳化系統當然還是選擇最佳化模式。LAZY模式最好的應該是使用Lazy<T>簡短安全。
以上都是我在部落格園對單例模式學習的總結。今後會補充一些具體的項目中的案例讓和我一樣的新手更容易吸收理解最終達到舉一反三。這裡重點參考了這裡的文章,希望大家有指教的地方多多提點。感謝。
C# 單例模式(Singleton Pattern)