單例模式(Singleton)的6種實現

1.摘要
   在咱們平常的工做中常常須要在應用程序中保持一個惟一的實例,如:IO處理,數據庫操做等,因爲這些對象都要佔用重要的系統資源,因此咱們必須限制這些實例的建立或始終使用一個公用的實例,這就是——單例模式(Singleton)。
                         使用頻率使用頻率html

   單例模式(Singleton):保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。數據庫


2.正文安全

圖1單例模式(Singleton)結構圖服務器

   單例模式(Singleton)是幾個建立模式中最對立的一個,它的主要特色不是根據用戶程序調用生成一個新的實例,而是控制某個類型的實例惟一性,經過上圖咱們知道它包含的角色只有一個,就是Singleton,它擁有一個私有構造函數,這確保用戶沒法經過new直接實例它。除此以外,該模式中包含一個靜態私有成員變量instance與靜態公有方法Instance()。Instance()方法負責檢驗並實例化本身,而後存儲在靜態成員變量中,以確保只有一個實例被建立。多線程

圖2單例模式(Singleton)邏輯模型less


   接下來咱們將介紹6中不一樣的單例模式(Singleton)的實現方式。這些實現方式都有如下的共同點:dom

   1.有一個私有的無參構造函數,這能夠防止其餘類實例化它,並且單例類也不該該被繼承,若是單例類容許繼承那麼每一個子類均可以建立實例,這就違背了Singleton模式「惟一實例」的初衷。
   2.單例類被定義爲sealed,就像前面提到的該類不該該被繼承,因此爲了保險起見能夠把該類定義成不容許派生,但沒有要求必定要這樣定義。
   3.一個靜態的變量用來保存單實例的引用。
   4.一個公有的靜態方法用來獲取單實例的引用,若是實例爲null即建立一個。ide



版本一線程不安全
函數

  
  
           
  
  
  1. // Bad code! Do not use!性能

  2. publicsealedclass Singleton

  3. {

  4. privatestatic Singleton instance=null;

  5. private Singleton()

  6.    {

  7.    }

  8. publicstatic Singleton Instance

  9.    {

  10. get

  11.        {

  12. if (instance==null)

  13.            {

  14.                instance = new Singleton();

  15.            }

  16. return instance;

  17.        }

  18.    }

  19. }

   以上的實現方式適用於單線程環境,由於在多線程的環境下有可能獲得Singleton類的多個實例。假如同時有兩個線程去判斷(instance==null),而且獲得的結果爲真,那麼兩個線程都會建立類Singleton的實例,這樣就違背了Singleton模式「惟一實例」的初衷。

版本二線程安全

  
  
           
  
  
  1. publicsealedclass Singleton

  2. {

  3. privatestatic Singleton instance = null;

  4. privatestaticreadonlyobject padlock = newobject();

  5.    Singleton()

  6.    {

  7.    }

  8. publicstatic Singleton Instance

  9.    {

  10. get

  11.        {

  12. lock (padlock)

  13.            {

  14. if (instance == null)

  15.                {

  16.                    instance = new Singleton();

  17.                }

  18. return instance;

  19.            }

  20.        }

  21.    }

  22. }

   以上方式的實現方式是線程安全的,首先咱們建立了一個靜態只讀的進程輔助對象,因爲lock是確保當一個線程位於代碼的臨界區時,另外一個線程不能進入臨界區(同步操做)。若是其餘線程試圖進入鎖定的代碼,則它將一直等待,直到該對象被釋放。從而確保在多線程下不會建立多個對象實例了。只是這種實現方式要進行同步操做,這將是影響系統性能的瓶頸和增長了額外的開銷。

版本三 Double-Checked Locking
   前面講到的線程安全的實現方式的問題是要進行同步操做,那麼咱們是否能夠下降經過操做的次數呢?其實咱們只需在同步操做以前,添加判斷該實例是否爲null就能夠下降經過操做的次數了,這樣是經典的Double-Checked Locking方法。

  
  
           
  
  
  1. // Bad code! Do not use!

  2. publicsealedclass Singleton

  3. {

  4. privatestatic Singleton instance = null;

  5. privatestaticreadonlyobject padlock = newobject();

  6.    Singleton()

  7.    {

  8.    }

  9. publicstatic Singleton Instance

  10.    {

  11. get

  12.        {

  13. if (instance == null)

  14.            {

  15. lock (padlock)

  16.                {

  17. if (instance == null)

  18.                    {

  19.                        instance = new Singleton();

  20.                    }

  21.                }

  22.            }

  23. return instance;

  24.        }

  25.    }

  26. }


   在介紹第四種實現方式以前,首先讓咱們認識什麼是beforefieldinit,當字段被標記爲beforefieldinit類型時,該字段初始化能夠發生在任什麼時候候任何字段被引用以前。這句話聽起了有點彆扭,接下來讓咱們經過具體的例子介紹。

  
  
           
  
  
  1. /// <summary>

  2. /// Defines a test class.

  3. /// </summary>

  4. class Test

  5. {

  6. publicstaticstring x = EchoAndReturn("In type initializer");

  7. publicstaticstring EchoAndReturn(string s)

  8.    {

  9.        Console.WriteLine(s);

  10. return s;

  11.    }

  12. }

   上面咱們定義了一個包含靜態字段和方法的類Test,但要注意咱們並無定義靜態的構造函數。

圖3 Test類的IL代碼


  
  
           
  
  
  1. class Test

  2. {

  3. publicstaticstring x = EchoAndReturn("In type initializer");

  4. // Defines a parameterless constructor.

  5. static Test()

  6.    {

  7.    }

  8. publicstaticstring EchoAndReturn(string s)

  9.    {

  10.        Console.WriteLine(s);

  11. return s;

  12.    }

  13. }


上面咱們給Test類添加一個靜態的構造函數。


圖4 Test類的IL代碼

   經過上面Test類的IL代碼的區別咱們發現,當Test類包含靜態字段,並且沒有定義靜態的構造函數時,該類會被標記爲beforefieldinit。

   如今也許有人會問:「被標記爲beforefieldinit和沒有標記的有什麼區別呢」?OK如今讓咱們經過下面的具體例子看一下它們的區別吧!


  
  
           
  
  
  1. class Test

  2. {

  3. publicstaticstring x = EchoAndReturn("In type initializer");

  4. publicstaticstring EchoAndReturn(string s)

  5.    {

  6.        Console.WriteLine(s);

  7. return s;

  8.    }

  9. }

  10. class Driver

  11. {

  12. publicstaticvoid Main()

  13.    {

  14.        Console.WriteLine("Starting Main");

  15. // Invoke a static method on Test

  16.        Test.EchoAndReturn("Echo!");

  17.        Console.WriteLine("After echo");

  18.        Console.ReadLine();

  19. // The output result:

  20. // Starting Main

  21. // In type initializer

  22. // Echo!

  23. // After echo          

  24.    }

  25. }

我相信你們均可以獲得答案,若是在調用EchoAndReturn()方法以前,須要完成靜態成員的初始化,因此最終的輸出結果以下:


接着咱們在Main()方法中添加string y = Test.x,以下:

  
  
           
  
  
  1. publicstaticvoid Main()

  2. {

  3.    Console.WriteLine("Starting Main");

  4. // Invoke a static method on Test

  5.    Test.EchoAndReturn("Echo!");

  6.    Console.WriteLine("After echo");

  7. //Reference a static field in Test

  8. string y = Test.x;

  9. //Use the value just to avoid compiler cleverness

  10. if (y != null)

  11.    {

  12.        Console.WriteLine("After field access");

  13.    }

  14.    Console.ReadKey();

  15. // The output result:

  16. // In type initializer

  17. // Starting Main

  18. // Echo!

  19. // After echo

  20. // After field access

  21. }

經過上面的輸出結果,你們能夠發現靜態字段的初始化跑到了靜態方法調用以前,不可思議啊!

最後咱們在Test類中添加一個靜態構造函數以下:

  
  
           
  
  
  1. class Test  

  2. {  

  3. publicstaticstring x = EchoAndReturn("In type initializer");  

  4. static Test()  

  5.    {  

  6.    }  

  7. publicstaticstring EchoAndReturn(string s)  

  8.    {  

  9.        Console.WriteLine(s);  

  10. return s;  

  11.    }  

  12. }  

   理論上,type initializer應該發生在」Echo!」以後和」After echo」以前,但這裏卻出現了不惟一的結果,只有當Test類包含靜態構造函數時,才能確保type initializer的初始化發生在」Echo!」以後和」After echo」以前。

   因此說要確保type initializer發生在被字段引用時,咱們應該給該類添加靜態構造函數。接下來讓咱們介紹單例模式的靜態方式。


版本四 靜態初始化

  
  
           
  
  
  1. publicsealedclass Singleton

  2. {

  3. privatestaticreadonly Singleton _instance = new Singleton();

  4. // Explicit static constructor to tell C# compiler

  5. // not to mark type as beforefieldinit

  6. static Singleton()

  7.    {

  8.    }

  9. /// <summary>

  10. /// Prevents a default instance of the

  11. /// <see cref="Singleton"/> class from being created.

  12. /// </summary>

  13. private Singleton()

  14.    {

  15.    }

  16. /// <summary>

  17. /// Gets the instance.

  18. /// </summary>

  19. publicstatic Singleton Instance

  20.    {

  21. get

  22.        {

  23. return _instance;

  24.        }

  25.    }

  26. }


   以上方式實現比以前介紹的方式都要簡單,但它確實是多線程環境下,C#實現的Singleton的一種方式。因爲這種靜態初始化的方式是在本身的字段被引用時纔會實例化。

   讓咱們經過IL代碼來分析靜態初始化。

靜態初始化IL代碼

   首先這裏沒有beforefieldinit的修飾符,因爲咱們添加了靜態構造函數當靜態字段被引用時才進行初始化,所以即使不少線程試圖引用_instance,也須要等靜態構造函數執行完並把靜態成員_instance實例化以後可使用。


版本五 延遲初始化

  
  
           
  
  
  1. publicsealedclass Singleton

  2. {

  3. private Singleton()

  4.    {

  5.    }

  6. publicstatic Singleton Instance { get { return Nested.instance; } }

  7. privateclass Nested

  8.    {

  9. // Explicit static constructor to tell C# compiler

  10. // not to mark type as beforefieldinit

  11. static Nested()

  12.        {

  13.        }

  14. internalstaticreadonly Singleton instance = new Singleton();

  15.    }

  16. }

這裏咱們把初始化工做放到Nested類中的一個靜態成員來完成,這樣就實現了延遲初始化。


版本六Lazy<T> type

  
  
           
  
  
  1. /// <summary>

  2. /// .NET 4's Lazy<T> type

  3. /// </summary>

  4. publicsealedclass Singleton

  5. {

  6. privatestaticreadonly Lazy<Singleton> lazy =

  7. new Lazy<Singleton>(() => new Singleton());

  8. publicstatic Singleton Instance { get { return lazy.Value; } }

  9. private Singleton()

  10.    {

  11.    }

  12. }



這種方式的簡單和性能良好,並且還提供檢查是否已經建立實例的屬性IsValueCreated。

具體例子

   如今讓咱們使用單例模式(Singleton)實現負載平衡器,首先咱們定義一個服務器類,它包含服務器名和IP地址以下:

  
  
           
  
  
  1. /// <summary>

  2. /// Represents a server machine

  3. /// </summary>

  4. class Server

  5. {

  6. // Gets or sets server name

  7. publicstring Name { get; set; }

  8. // Gets or sets server IP address

  9. publicstring IP { get; set; }

  10. }


 因爲負載平衡器只提供一個對象實例供服務器使用,因此咱們使用單例模式(Singleton)實現該負載平衡器。

  
  
           
  
  
  1. /// <summary>

  2. /// The 'Singleton' class

  3. /// </summary>

  4. sealedclass LoadBalancer

  5. {

  6. privatestaticreadonly LoadBalancer _instance =

  7. new LoadBalancer();

  8. // Type-safe generic list of servers

  9. private List<Server> _servers;

  10. private Random _random = new Random();

  11. static LoadBalancer()

  12.    {

  13.    }

  14. // Note: constructor is 'private'

  15. private LoadBalancer()

  16.    {

  17. // Load list of available servers

  18.        _servers = new List<Server>

  19.            {

  20. new Server{ Name = "ServerI", IP = "192.168.0.108" },

  21. new Server{ Name = "ServerII", IP = "192.168.0.109" },

  22. new Server{ Name = "ServerIII", IP = "192.168.0.110" },

  23. new Server{ Name = "ServerIV", IP = "192.168.0.111" },

  24. new Server{ Name = "ServerV", IP = "192.168.0.112" },

  25.            };

  26.    }

  27. /// <summary>

  28. /// Gets the instance through static initialization.

  29. /// </summary>

  30. publicstatic LoadBalancer Instance

  31.    {

  32. get { return _instance; }

  33.    }

  34. // Simple, but effective load balancer

  35. public Server NextServer

  36.    {

  37. get

  38.        {

  39. int r = _random.Next(_servers.Count);

  40. return _servers[r];

  41.        }

  42.    }

  43. }

上面負載平衡器類LoadBalancer咱們使用靜態初始化方式實現單例模式(Singleton)。

  
  
           
  
  
  1. staticvoid Main()

  2. {

  3.    LoadBalancer b1 = LoadBalancer.Instance;

  4.    b1.GetHashCode();

  5.    LoadBalancer b2 = LoadBalancer.Instance;

  6.    LoadBalancer b3 = LoadBalancer.Instance;

  7.    LoadBalancer b4 = LoadBalancer.Instance;

  8. // Confirm these are the same instance

  9. if (b1 == b2 && b2 == b3 && b3 == b4)

  10.    {

  11.        Console.WriteLine("Same instance\n");

  12.    }

  13. // Next, load balance 15 requests for a server

  14.    LoadBalancer balancer = LoadBalancer.Instance;

  15. for (int i = 0; i < 15; i++)

  16.    {

  17. string serverName = balancer.NextServer.Name;

  18.        Console.WriteLine("Dispatch request to: " + serverName);

  19.    }

  20.    Console.ReadKey();

  21. }

3 總結


單例模式的優勢:

單例模式(Singleton)會控制其實例對象的數量,從而確保訪問對象的惟一性。

   實例控制:單例模式防止其它對象對本身的實例化,確保全部的對象都訪問一個實例。
   伸縮性:由於由類本身來控制實例化進程,類就在改變實例化進程上有相應的伸縮性。

單例模式的缺點:

   a)系統開銷。雖然這個系統開銷看起來很小,可是每次引用這個類實例的時候都要進行實例是否存在的檢查。這個問題能夠經過靜態實例來解決。
   b)開發混淆。當使用一個單例模式的對象的時候(特別是定義在類庫中的),開發人員必需要記住不能使用new關鍵字來實例化對象。由於開發者看不到在類庫中的源代碼,因此當他們發現不能實例化一個類的時候會很驚訝。
   c)對象生命週期。單例模式沒有提出對象的銷燬。在提供內存管理的開發語言(好比,基於.NetFramework的語言)中,只有單例模式對象本身才能將對象實例銷燬,由於只有它擁有對實例的引用。在各類開發語言中,好比C++,其它類能夠銷燬對象實例,可是這麼作將致使單例類內部的指針指向不明。

單例適用性

   使用Singleton模式有一個必要條件:在一個系統要求一個類只有一個實例時才應當使用單例模式。反之,若是一個類能夠有幾個實例共存,就不要使用單例模式。

   不要使用單例模式存取全局變量。這違背了單例模式的用意,最好放到對應類的靜態成員中。

   不要將數據庫鏈接作成單例,由於一個系統可能會與數據庫有多個鏈接,而且在有鏈接池的狀況下,應當儘量及時釋放鏈接。Singleton模式因爲使用靜態成員存儲類實例,因此可能會形成資源沒法及時釋放,帶來問題。




參考:

http://www.cnblogs.com/rush/archive/2011/10/30/2229565.html

http://csharpindepth.com/Articles/General/Singleton.aspx

相關文章
相關標籤/搜索