天天看點

小菜學習設計模式(二)—單例(Singleton)模式

前言

設計模式目錄:

  • 小菜學習設計模式(一)—模闆方法(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

  關于模式,再多說兩句,在某些情況下,像上面所說的:模式可以看成現實生活中的模具,有些産品(項目)是由一種模具(模式)生成出來的,比如杯子、瓶子等,有些産品(項目)是由多種模具(模式)生成出來,然後組合而成的,比如汽車、飛機等,是由成千上萬個零部件組合形成的。就是說學會模式後要會懂得組合,而且要“合适”的組合,這樣才會做出一個完善的産品(項目)。

  還是那就話:騷年們,和小菜一起整理學習吧,未完待續。。。

作者:田園裡的蟋蟀

微信公衆号:你好架構

出處:http://www.cnblogs.com/xishuai/

公衆号會不定時的分享有關架構的方方面面,包含并不局限于:Microservices(微服務)、Service Mesh(服務網格)、DDD/TDD、Spring Cloud、Dubbo、Service Fabric、Linkerd、Envoy、Istio、Conduit、Kubernetes、Docker、MacOS/Linux、Java、.NET Core/ASP.NET Core、Redis、RabbitMQ、MongoDB、GitLab、CI/CD(持續內建/持續部署)、DevOps等等。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接。

分享到:

QQ空間

新浪微網誌

騰訊微網誌

微信

更多