單例模式:一個類在記憶體中只有一個對象(實例),並且提供一個可以全局訪問或者獲取這個對象的方法。 這兩天學的,寫了個小例子,問了同事一些關於線程的問題,還有從網上查了一些資料。還犯了一些低級的錯誤。 vs2017控制台輸出文字亂碼,從網上找了一些方法不管用,最後發現是自己新建項目選錯模板了,選擇了.N ...
單例模式:一個類在記憶體中只有一個對象(實例),並且提供一個可以全局訪問或者獲取這個對象的方法。
這兩天學的,寫了個小例子,問了同事一些關於線程的問題,還有從網上查了一些資料。還犯了一些低級的錯誤。
vs2017控制台輸出文字亂碼,從網上找了一些方法不管用,最後發現是自己新建項目選錯模板了,選擇了.NET CORE的模板,所以才會輸出亂碼,大家一定要吸取教訓。
直接上代碼
演示類,Person.cs
public class Person { /// <summary> /// 實例化一個私有靜態變數,存儲類本身的實例 /// </summary> private static Person _person = null; /// <summary> /// 構造函數 /// </summary> private Person() { Console.WriteLine("構造了一個{0}",GetType().Name); } public static Person GetInstance() { if(_person == null) _person = new Person(); return _person; } }
客戶端代碼:
{ var person1 = Person.GetInstance(); var person2 = Person.GetInstance(); var person3 = Person.GetInstance();
Console.WriteLine("person1 == person2:{0}", object.ReferenceEquals(person1, person2)); }
輸出結果:
只輸出了一次,兩個對象引用相等。說明單例模式沒問題。
進階:
public class Person { /// <summary> /// 實例化一個私有靜態變數,存儲類本身的實例 /// </summary> private static Person _person = null; /// <summary> /// 構造函數 /// </summary> private Person() { Console.WriteLine("構造了一個{0}",GetType().Name); } public static Person GetInstance() { if (_person == null) _person = new Person(); return _person; } }
客戶端調用代碼:
{ Person person1 = null; Person person2 = null; Person person3 = null; //多線程下可以輸出多次 var thread1 = new Thread(() => { person1 = Person.GetInstance(); }); var thread2 = new Thread(() => { person2 = Person.GetInstance(); }); var thread3 = new Thread(() => { person3 = Person.GetInstance(); }); thread1.Start(); thread2.Start(); thread3.Start(); Thread.Sleep(1000);//等待子線程完成 Console.WriteLine("person1 == person2:{0}", object.ReferenceEquals(person1, person2)); }
輸出結果:
輸出了多次,引用也不相等。說明多次實例化這個類,單例模式寫的不完全正確,那讓我們加上線程安全驗證。
繼續進階:
public class Person { /// <summary> /// 實例化一個私有靜態變數,存儲類本身的實例 /// </summary> private static Person _person = null; /// <summary> /// 作為鎖的對象,使用私有的、靜態的並且是只讀的對象 /// </summary> private static readonly object _obj = new object(); /// <summary> /// 構造函數 /// </summary> private Person() { Console.WriteLine("構造了一個{0}",GetType().Name); } /// <summary> /// 獲取類唯一的實例對象 /// </summary> public static Person GetInstance() { if (_person == null)//先判斷是否為空 { lock (_obj)//再判斷下是否有別的線程在使用 { if (_person == null)//等其他線程使用完成後再判斷是否為空 { _person = new Person(); } } } return _person; } }
客戶端調用代碼:
{ //使用鎖,鎖住的對象:使用私有的、靜態的並且是只讀的對象 Person person1 = null; Person person2 = null; Person person3 = null; //多線程下可以輸出多次 var thread1 = new Thread(() => { person1 = Person.GetInstance(); }); var thread2 = new Thread(() => { person2 = Person.GetInstance(); }); var thread3 = new Thread(() => { person3 = Person.GetInstance(); }); thread1.Start(); thread2.Start(); thread3.Start(); Thread.Sleep(1000);//等待子線程完成 Console.WriteLine("person1 == person2:{0}", object.ReferenceEquals(person1, person2)); }
輸出結果:
輸出一次,引用相等,說明單例模式成功,線程安全已經加上。
進階2
可以使用靜態構造函數作為單例模式:
public class Person { /// <summary> /// 實例化一個私有靜態變數,存儲類本身的實例 /// </summary> private static Person _person = null; /// <summary> /// 構造函數 /// </summary> private Person() { Console.WriteLine("構造了一個{0}",GetType().Name); } /// <summary> /// 靜態構造函數,只執行一次 /// </summary> static Person() { _person = new Person(); } /// <summary> /// 獲取類的實例 /// </summary> public static Person GetInstance() { return _person; } }
客戶端代碼:
{ //使用鎖,鎖住的對象:使用私有的、靜態的並且是只讀的對象 //使用靜態構造函數,在裡面初始化person對象 Person person1 = null; Person person2 = null; Person person3 = null; //多線程下可以輸出多次 var thread1 = new Thread(() => { person1 = Person.GetInstance(); }); var thread2 = new Thread(() => { person2 = Person.GetInstance(); }); var thread3 = new Thread(() => { person3 = Person.GetInstance(); }); thread1.Start(); thread2.Start(); thread3.Start(); Thread.Sleep(1000);//等待子線程完成 Console.WriteLine("person1 == person2:{0}", object.ReferenceEquals(person1, person2)); }
輸出結果:
輸出一次,引用相等,靜態構造函數也可以作為單例模式實現的一種方法。