.NET單例模式-------各種寫法&&驗證
前言
單例模式對大家來說都不陌生,也很容易搞懂其原理,本篇文章也不提供單例模式的詳細原理解析,本篇文章的目的是展示在C#中單例模式的各種實現方案(不完全,只是最通用的方式)以及其特點的驗證(是不是真的線程安全,是不是真的延遲初始化?),寫單例模式的文章都很多了,各種語言,但是很多地方都只說:本方式支持多線程、支持延遲初始化等,也有很多也提供為什么支持,下面我對所有大家通常使用的幾種單例模式方案進行講解和驗證!有哪里不對的地方,希望能得到尊敬的讀者們拍磚反饋,覺得好,順帶推薦一下,謝謝。
簡單原理解析
單例模式,目標在於確保一個類僅僅能產生一個實例,並且提供一個全局訪問點,獲取該實例。
無論哪一種單例模式變種,都離不開制作步驟這個中心。就好像無論哪家雞爪店,其制作雞爪方法都大同小異(都要先拿到雞爪,洗雞爪,弄熟雞爪)。我們單例模式其實一樣,其中心步驟包括:限制外部new出該對象的實例,內部提供該類型的一個唯一對象,提供一個全局訪問點讓外界獲取到該唯一對象的實例進行操作。
其實就這么簡單,下面我要分析4個主要變種單例模式並且分別進行驗證。
准備工作
我先提供一個大概框架給大家,方便用於測試,也可以不下載,繼續看下去。 測試模板下載
提供的模板很簡單,只有一個類Person,下面給出要點:
1.構造函數是私有的(避免new出新實例)。
2.在構造函數里,我寫的Console.WriteLine(主要是觀察這個類的實例是何時初始化的,初始化了多少次)。
3.靜態方法getName()的作用是:在還沒有通過全局訪問點獲取實例之前,調用這個getName方法,內部的實例會不會被初始化,如果不會,證明延遲初始化了,如果會,證明沒有延遲初始化。
下面先給出一個例子,這個例子是餓漢式單例模式。
public class Person { /*餓漢式單例(線程安全,不支持延遲初始化)*/ //初始化的時候會有反應,應用於延遲初始化的驗證 private Person() { Console.WriteLine("我初始化了"); } private static String name = "Jarvin"; //內部的唯一實例 private static Person instance = new Person(); //全局訪問點,用於獲取唯一實例 public static Person getInstance() { return instance; } //實例方法 public void Say() { Console.WriteLine("我是{0}",name); } /*靜態的方法,應用於延遲初始化驗證 * 如果調用該方法之前還沒初始,延遲初始化 * 如果調用該方法之前初始化了,沒有延遲初始化 */ public static String getName() { return name; } }
測試的方式,要點:
1.測試是否延遲初始化:測試方法是先調用Person.getName(),看結果返回name之前有沒有被初始化來判斷。
2.測試線程安全:開3個多線程新任務,任務內容是獲取唯一實例,並且調用實例方法。
下面給出Main方法的代碼:
class Program { static void Main(string[] args) { Console.WriteLine(Person.getName()); Console.WriteLine("下面進入多線程模式"); for (int i = 0; i < 3; i++) { Task.Factory.StartNew(letPersonSay); } Console.ReadKey(); } private static void letPersonSay() { Person emperor = Person.getInstance(); emperor.Say(); } }
注意:單例模式秀中中出現的以下代碼只是測試需要使用,在正式的使用場景要去掉。
int i=50000000; while (i > 0) { i--; }
單例模式秀
1.餓漢式單例
初步判斷:不支持延遲初始化,線程安全。
private Person() { Console.WriteLine("我初始化了"); } private static String name = "Jarvin"; private static Person instance = new Person(); public static Person getInstance() { int i = 50000000; while (i > 0) { i--; } return instance; } public void Say() { Console.WriteLine("我是{0}",name); } public static String getName() { return name; }
驗證:
分析結果:在靜態方法執行之前(Jarvin字符串)先初始化,不支持延遲初始化。然后進入多線程,沒有問題。驗證通過。
2.懶漢式單例
初步判斷:支持延遲初始化,線程不安全。
private Person() { Console.WriteLine("我初始化了"); } private static String name = "Jarvin"; private static Person instance; public static Person getInstance() { if (instance == null) { int i=50000000; while (i > 0) { i--; } instance = new Person(); } return instance; } public void Say() { Console.WriteLine("我是{0}",name); } public static String getName() { return name; }
驗證:
分析結果:在調用靜態方法之前並沒有先初始化,所以支持延遲初始化。進入多線程以后,有出現兩次初始化,創建了兩個Person類實例,線程不安全。驗證通過。
3.內部類式單例
初步判斷:支持延遲初始化,線程安全
private Person() { Console.WriteLine("我初始化了"); } public static Person getInstance() { return SingleHelper.GetEmperor(); } private class SingleHelper { private static Person emperor = new Person(); public static Person GetEmperor() { int i = 50000000; while (i > 0) { i--; } return emperor; } } private static string name = "Jarvin"; public void Say() { Console.WriteLine("我是{0}", name); } public static String getName() { return name; }
驗證:
分析結果:在調用靜態方法之前並沒有先初始化,所以支持延遲初始化。進入多線程以后,只初始化一次,線程安全。驗證通過。
4.雙檢查式單例
初步判斷:支持延遲初始化,線程安全
private Person() { Console.WriteLine("我初始化了"); } public static object Flag = new object(); public static Person me; public static Person getInstance() { if (me == null) { lock (Flag) { if (me == null) { int i = 50000000; while (i > 0) { i--; } me = new Person(); } } } return me; } private static string name = "Jarvin"; public void Say() { Console.WriteLine("我是{0}", name); } public static String getName() { return name; }
驗證:
分析結果:在調用靜態方法之前並沒有先初始化,所以支持延遲初始化。進入多線程以后,只初始化一次,線程安全。驗證通過。
總結
其實單例模式非常簡單,聰明的讀者們看到這里應該對大概通過的這四種單例,以及其特性都了解了。見笑啦,下面提供全部測試的源碼,有興趣的可以收藏。
