單例前端
在軟件系統中,常常有這樣一些特殊的類,必須保證它們在系統中只存在一個實例(eg:應對一些特殊狀況,好比數據庫鏈接池(內置了資源) 全局惟一號碼生成器),才能確保它們的邏輯正確性、以及良好的效率。數據庫
優勢:單例的好處就是單例,就是全局惟一的一個實例
單例模式會阻止其餘對象實例化其本身的單例對象的副本,從而確保全部對象都訪問惟一實例安全
缺點:單例能夠避免重複建立,可是也會常駐內存 除非是真的有必要,不然不要單例併發
使用單例對象(尤爲在類庫中定義的對象)時,開發人員必須記住本身不能使用new關鍵字實例化對象。由於可能沒法訪問庫源代碼,所以應用程序開發人員可能會意外發現本身沒法直接實例化此類ide
如何繞過常規的構造器,提供一種機制來保證一個類只建立一個實例?函數
如何實現?將構造函數私有化,而後對外提供一個公開的靜態方法,使用一個靜態屬性進行判斷當前對象是否被建立測試
1 // 不要用這種方式 2 public class Singleton 3 { 4 private static Singleton _instance = null; 5 private Singleton() { } 6 public static Singleton CreateInstance() 7 { 8 if (_instance == null) 9 { 10 _instance = new Singleton(); 11 } 12 return _instance; 13 } 14 }
上面的方法是非線程安全的,多個不一樣的線程能夠同時進入這個方法,若是instance爲空的而且這裏返回真的狀況下,均可以建立實例,這顯然違反了單例模式,實際上,在測試之前,實例就已經有可能被建立了,可是內存模型不能保證這個實例能被其餘的線程看到,除非合適的內存屏障已經被跨過了優化
咱們把上面的代碼優化一下this
1 /// <summary> 2 /// 單例類:一個構造對象很耗時耗資源類型 3 /// 懶漢式單例模式 4 /// </summary> 5 public class Singleton 6 { 7 /// <summary> 8 /// 構造函數耗時耗資源 9 /// </summary> 10 private Singleton() 11 { 12 long lResult = 0; 13 for (int i = 0; i < 10000000; i++) 14 { 15 lResult += i; 16 } 17 Thread.Sleep(2000); 18 Console.WriteLine("{0}被構造一次", this.GetType().Name); 19 } 20 /// <summary> 21 /// 3 全局惟一靜態 重用這個變量 22 /// </summary> 23 private static volatile Singleton _Singleton = null; 24 //volatile 促進線程安全 讓線程按順序操做 25 private static readonly object Singleton_Lock = new object(); 26 /// <summary> 27 /// 2 公開的靜態方法提供對象實例 28 /// </summary> 29 /// <returns></returns> 30 public static Singleton CreateInstance() 31 { 32 if (_Singleton == null)//是_Singleton已經被初始化以後,就不要進入鎖等待了 33 { 34 lock (Singleton_Lock) 35 //保證任意時刻只有一個線程進入lock範圍 36 //也限制了併發,尤爲是_Singleton已經被初始化以後 37 { 38 //Thread.Sleep(1000); 39 //Console.WriteLine("等待鎖1s以後才繼續。。。"); 40 if (_Singleton == null)//保證只實例化一次 41 { 42 _Singleton = new Singleton(); 43 } 44 } 45 } 46 return _Singleton; 47 } 48 49 //既然是單例,你們用的是同一個對象,用的是同一個方法,那還會併發嗎 還有線程安全問題嗎? 50 public int iTotal = 0; 51 public void Show() 52 { 53 //lock (Singleton_Lock) 54 //{ 55 this.iTotal++; 56 //} 57 } 58 59 public static void Test() 60 { 61 Console.WriteLine("Test1"); 62 Console.WriteLine(_Singleton.iTotal); 63 } 64 65 }
前端調用spa
1 { 2 List<Task> tasks = new List<Task>(); 3 for (int i = 0; i < 10000; i++) 4 { 5 tasks.Add(Task.Run(() => 6 { 7 Singleton singleton = Singleton.CreateInstance(); 8 singleton.Show(); 9 })); 10 } 11 Task.WaitAll(tasks.ToArray()); 12 Singleton.Test(); 13 //iTotal 是0 1 10000 仍是其餘的 14 //其餘值,1到10000範圍內均可能 線程不安全 15 16 }
運行代碼咱們會發現一個問題
iTotal 是0 1 10000 仍是其餘的,
其餘值,1到10000範圍內均可能 線程不安全
爲何呢?形成這種狀況的緣由單例執行singleton.Show()方法時 iTotal在等於某個值時被附加屢次,由此獲得結論:
即便是單例,變量也不是線程安全的,單例不是爲了保證線程安全
如何優化?給show方法加把鎖
1 public void Show() 2 { 3 lock (Singleton_Lock) 4 { 5 this.iTotal++; 6 } 7 }
單例還有另外的寫法,以上是懶漢式單例模式,下面咱們來看看餓漢式
利用靜態構造函數 程序第一次使用這個類型前被調用,且只調用一次
1 /// <summary> 2 /// 單例類:一個構造對象很耗時耗資源類型 3 /// 4 /// 餓漢式 5 /// </summary> 6 public class SingletonSecond 7 { 8 /// <summary> 9 /// 1 構造函數耗時耗資源 10 /// </summary> 11 private SingletonSecond() 12 { 13 long lResult = 0; 14 for (int i = 0; i < 10000000; i++) 15 { 16 lResult += i; 17 } 18 Thread.Sleep(1000); 19 Console.WriteLine("{0}被構造一次", this.GetType().Name); 20 } 21 /// <summary> 22 /// 靜態構造函數:由CLR保證,程序第一次使用這個類型前被調用,且只調用一次 23 /// 24 /// 檢測,初始化 25 /// 寫日誌功能的文件夾檢測 26 /// XML配置文件 27 /// </summary> 28 static SingletonSecond() 29 { 30 _SingletonSecond = new SingletonSecond(); 31 Console.WriteLine("SingletonSecond 被啓動"); 32 } 33 34 35 private static SingletonSecond _SingletonSecond = null; 36 public static SingletonSecond CreateInstance() 37 { 38 return _SingletonSecond; 39 }//餓漢式 只要使用類就會被構造 40 41 42 }
另一種相似的,利用靜態字段建立對象
1 /// <summary> 2 /// 單例類:一個構造對象很耗時耗資源類型 3 /// 餓漢式 4 /// </summary> 5 public class SingletonThird 6 { 7 /// <summary> 8 /// 構造函數耗時耗資源 9 /// </summary> 10 private SingletonThird() 11 { 12 long lResult = 0; 13 for (int i = 0; i < 10000000; i++) 14 { 15 lResult += i; 16 } 17 Thread.Sleep(1000); 18 Console.WriteLine("{0}被構造一次", this.GetType().Name); 19 } 20 21 /// <summary> 22 /// 靜態字段:在第一次使用這個類以前,由CLR保證,初始化且只初始化一次 23 /// 這個比今天構造函數還早 24 /// </summary> 25 private static SingletonThird _SingletonThird = new SingletonThird();//打印個日誌 26 public static SingletonThird CreateInstance() 27 { 28 return _SingletonThird; 29 }//餓漢式 只要使用類就會被構造 30 31 32 33 34 public void Show() 35 { 36 Console.WriteLine("這裏是{0}.Show", this.GetType().Name); 37 } 38 39 }