23種設計模式之單例(Singleton Pattern)

單例前端

在軟件系統中,常常有這樣一些特殊的類,必須保證它們在系統中只存在一個實例(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 }
View Code

上面的方法是非線程安全的,多個不一樣的線程能夠同時進入這個方法,若是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     }
View Code

前端調用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                 }
View Code

 運行代碼咱們會發現一個問題

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         }
View Code

單例還有另外的寫法,以上是懶漢式單例模式,下面咱們來看看餓漢式

利用靜態構造函數 程序第一次使用這個類型前被調用,且只調用一次

 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     }
View Code

另一種相似的,利用靜態字段建立對象

 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     }
View Code

 本文參考文檔:https://csharpindepth.com/Articles/Singleton#unsafe

相關文章
相關標籤/搜索