前言
設計模式目錄:
- 小菜學習設計模式(一)—模板方法(Template)模式
- 小菜學習設計模式(二)—單例(Singleton)模式
- 小菜學習設計模式(三)—工廠方法(Factory Method)模式
- 小菜學習設計模式(四)—原型(Prototype)模式
- 小菜學習設計模式(五)—控制反轉(Ioc)
- 持續更新中。。。
本篇目錄:
單例模式(Singleton)可以說是最簡單的模式,對.net來說,因為不需要考慮到垃圾回收機制,實現起來很簡單,但是對於沒有提供內存管理的平台來說,比如C++,因為單例模式只考慮創建對象,所以使用的時候要考慮全面些。
其實說到些設計模式,我們有時候用到的真的很少,就像飛機零部件的模具不適用於汽車制造一樣,某些設計模式也只在特定的環境下使用,單例模式的使用場景一般是資源管理器等,像說的最多的就是打印機場景:每台計算機可以有若干個打印機,但只能有一個Printer Spooler,以避免兩個打印作業同時輸出到打印機中。每台計算機可以有若干傳真卡,但是只應該有一個軟件負責管理傳真卡,以避免出現兩份傳真作業同時傳到傳真卡中的情況。每台計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。說白點就是一個男人可以有很多女朋友,但是結婚生子的只能是其中一個。一夫多妻的情況就不是單例模式了,那應該是“多態”了。哈哈。
簡單實現
單例模式(Singleton)在.net中的定義是:一個類有且僅有一個實例,並且自行實例化向整個系統提供。
從定義中我們可以看出,單例模式所具有的三個要點:
- 某個類只能有一個實例
- 必須自行創建這個實例
- 必須自行向整個系統提供這個實例
根據所說的要點,我們可以在.net中這樣簡單的實現:
1 public class SingletonTest 2 { 3 public static SingletonTest model; 4 private SingletonTest() 5 { } 6 public static SingletonTest getSingleton() 7 { 8 if (model==null) 9 { 10 model = new SingletonTest(); 11 } 12 return model; 13 } 14 }
代碼就這么簡單,在getSingleton()方法返回實例的時候要先判斷對象是否已經被實例化,如果是就不需要重新創建了。
線程安全
上面的代碼看起來沒什么問題,但是在多線程的情況下就會出現問題,我們來開幾個線程測試下:
1 public class SingletonTest 2 { 3 public static SingletonTest model; 4 private SingletonTest() 5 { } 6 public static SingletonTest getSingleton() 7 { 8 if (model==null) 9 { 10 Console.WriteLine(String.Format("我是被線程:{0}創建的!", Thread.CurrentThread.Name)); 11 model = new SingletonTest(); 12 } 13 return model; 14 } 15 }
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Program p1 = new Program(); 6 p1.Test(); 7 Console.ReadLine(); 8 } 9 10 private void Test() 11 { 12 Thread newThread; 13 ThreadStart ts = new ThreadStart(DoWork); 14 for (int counter = 1; counter < 6; counter++) 15 { 16 newThread = new Thread(ts); 17 newThread.Name = "蟋蟀" + counter; 18 newThread.Start(); 19 } 20 } 21 22 protected void DoWork() 23 { 24 //調用返回對象方法 25 SingletonTest.getSingleton(); 26 } 27 }
執行結果:
根據上圖的執行結果,會發現SingletonTest對象被實例化了2次,按照單例模式(Singleton)的特性:一個類只能有一個實例,那就不是單例模式了,為什么會實例化兩次呢?因為我們的計算機執行速度很快,在某一個時間點,線程1在執行完if (model==null)這段代碼,還沒執行model = new SingletonTest(),線程2剛好執行判斷對象是否null,就是說線程1和線程2都會進入下面的if判斷體中實例化對象。
關於單例模式的線程安全問題,網上一找一大堆,在《漫談設計模式》這本書中,作者也提到了線程安全問題,java中是使用的是“Double-Check Locking”方法,還有序列化的問題,這邊先不考慮,其實在.net中解決線程安全的問題也很簡單,就是用lock鎖,我們根據上面的代碼,再來修改下,然后做個測試:
1 public class SingletonTest 2 { 3 private static SingletonTest singleton; 4 private static readonly object syncObject = new object(); 5 /// <summary> 6 /// 構造函數必須是私有的 7 /// 這樣在外部便無法使用 new 來創建該類的實例 8 /// </summary> 9 private SingletonTest() 10 { } 11 /// <summary> 12 /// 定義一個全局訪問點 13 /// 設置為靜態方法 14 /// 則在類的外部便無需實例化就可以調用該方法 15 /// </summary> 16 /// <returns></returns> 17 public static SingletonTest getSingleton() 18 { 19 //這里可以保證只實例化一次 20 //即在第一次調用時實例化 21 //以后調用便不會再實例化 22 //第一重 singleton == null 23 if (singleton == null) 24 { 25 lock (syncObject) 26 { 27 //第二重 singleton == null 28 if (singleton == null) 29 { 30 Console.WriteLine(String.Format("我是被線程:{0}創建的!", Thread.CurrentThread.Name)); 31 singleton = new SingletonTest(); 32 } 33 } 34 } 35 return singleton; 36 } 37 }
執行結果:
從上面的執行結果我們就可以看到,對象僅被實例化了一次,在某段代碼體中,只能有且只有一個線程訪問,加鎖的目的就好比:我們去火車站售票大廳買票,因為買票的人太多,為了緩解壓力,就多開了幾個售票窗口(線程),比如南京到徐州的G110次列車只有一張票,窗口A和窗口B的人同時都在買這一班次的票,這時候就要加鎖,不然就有可能會出現只有一張票,但是賣出去兩張。話題跑偏了,哈哈。
示例代碼下載:Singleton.rar
后記
關於模式,再多說兩句,在某些情況下,像上面所說的:模式可以看成現實生活中的模具,有些產品(項目)是由一種模具(模式)生成出來的,比如杯子、瓶子等,有些產品(項目)是由多種模具(模式)生成出來,然后組合而成的,比如汽車、飛機等,是由成千上萬個零部件組合形成的。就是說學會模式后要會懂得組合,而且要“合適”的組合,這樣才會做出一個完善的產品(項目)。
還是那就話:騷年們,和小菜一起整理學習吧,未完待續。。。