在咱們平常的工做中常常須要在應用程序中保持一個惟一的實例,如:IO處理,數據庫操做等,因爲這些對象都要佔用重要的系統資源,因此咱們必須限制這些實例的建立或始終使用一個公用的實例,這就是咱們今天要介紹的——單例模式(Singleton)。html
單件模式(Singleton):保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。設計模式
圖1單例模式(Singleton)結構圖安全
單例模式(Singleton)是幾個建立模式中最對立的一個,它的主要特色不是根據用戶程序調用生成一個新的實例,而是控制某個類型的實例惟一性,經過 上圖咱們知道它包含的角色只有一個,就是Singleton,它擁有一個私有構造函數,這確保用戶沒法經過new直接實例它。除此以外,該模式中包含一個 靜態私有成員變量instance與靜態公有方法Instance()。Instance()方法負責檢驗並實例化本身,而後存儲在靜態成員變量中,以確 保只有一個實例被建立。服務器
圖2單例模式(Singleton)邏輯模型多線程
接下來咱們將介紹6中不一樣的單例模式(Singleton)的實現方式。這些實現方式都有如下的共同點:less
/// <summary> /// A simple singleton class implements. /// </summary> public sealed class Singleton { private static Singleton _instance = null; /// <summary> /// Prevents a default instance of the /// <see cref="Singleton"/> class from being created. /// </summary> private Singleton() { } /// <summary> /// Gets the instance. /// </summary> public static Singleton Instance { get { return _instance ?? (_instance = new Singleton()); } } }
以上的實現方式適用於單線程環境,由於在多線程的環境下有可能獲得Singleton類的多個實例。假如同時有兩個線程去判斷(null == _singleton),而且獲得的結果爲真,那麼兩個線程都會建立類Singleton的實例,這樣就違背了Singleton模式「惟一實例」的初衷。dom
/// <summary> /// A thread-safe singleton class. /// </summary> public sealed class Singleton { private static Singleton _instance = null; private static readonly object SynObject = new object(); Singleton() { } /// <summary> /// Gets the instance. /// </summary> public static Singleton Instance { get { // Syn operation. lock (SynObject) { return _instance ?? (_instance = new Singleton()); } } } }
以上方式的實現方式是線程安全的,首先咱們建立了一個靜態只讀的進程輔助對象,因爲lock是確保當一個線程位於代碼的臨界區時,另外一個線程不能進入臨界 區(同步操做)。若是其餘線程試圖進入鎖定的代碼,則它將一直等待,直到該對象被釋放。從而確保在多線程下不會建立多個對象實例了。只是這種實現方式要進 行同步操做,這將是影響系統性能的瓶頸和增長了額外的開銷。ide
前面講到的線程安全的實現方式的問題是要進行同步操做,那麼咱們是否能夠下降經過操做的次數呢?其實咱們只需在同步操做以前,添加判斷該實例是否爲null就能夠下降經過操做的次數了,這樣是經典的Double-Checked Locking方法。函數
/// <summary> /// Double-Checked Locking implements a thread-safe singleton class /// </summary> public sealed class Singleton { private static Singleton _instance = null; // Creates an syn object. private static readonly object SynObject = new object(); Singleton() { } public static Singleton Instance { get { // Double-Checked Locking if (null == _instance) { lock (SynObject) { if (null == _instance) { _instance = new Singleton(); } } } return _instance; } } }
在介紹第四種實現方式以前,首先讓咱們認識什麼是,當字段被標記爲beforefieldinit類型時,該字段初始化能夠發生在任什麼時候候任何字段被引用以前。這句話聽起了有點彆扭,接下來讓咱們經過具體的例子介紹。
/// <summary> /// Defines a test class. /// </summary> class Test { public static string x = EchoAndReturn("In type initializer"); public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } }
上面咱們定義了一個包含靜態字段和方法的類Test,但要注意咱們並無定義靜態的構造函數。
圖3 Test類的IL代碼
class Test { public static string x = EchoAndReturn("In type initializer"); // Defines a parameterless constructor. static Test() { } public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } }
上面咱們給Test類添加一個靜態的構造函數。
圖4 Test類的IL代碼
經過上面Test類的IL代碼的區別咱們發現,當Test類包含靜態字段,並且沒有定義靜態的構造函數時,該類會被標記爲beforefieldinit。
如今也許有人會問:「被標記爲beforefieldinit和沒有標記的有什麼區別呢」?OK如今讓咱們經過下面的具體例子看一下它們的區別吧!
class Test { public static string x = EchoAndReturn("In type initializer"); static Test() { } public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } } class Driver { public static void Main() { Console.WriteLine("Starting Main"); // Invoke a static method on Test Test.EchoAndReturn("Echo!"); Console.WriteLine("After echo"); Console.ReadLine(); // The output result: // Starting Main // In type initializer // Echo! // After echo } }
我相信你們均可以獲得答案,若是在調用EchoAndReturn()方法以前,須要完成靜態成員的初始化,因此最終的輸出結果以下:
圖5輸出結果
接着咱們在Main()方法中添加string y = Test.x,以下:
public static void Main() { Console.WriteLine("Starting Main"); // Invoke a static method on Test Test.EchoAndReturn("Echo!"); Console.WriteLine("After echo"); //Reference a static field in Test string y = Test.x; //Use the value just to avoid compiler cleverness if (y != null) { Console.WriteLine("After field access"); } Console.ReadKey(); // The output result: // In type initializer // Starting Main // Echo! // After echo // After field access }
圖6 輸出結果
經過上面的輸出結果,你們能夠發現靜態字段的初始化跑到了靜態方法調用以前,Wo不可思議啊!
最後咱們在Test類中添加一個靜態構造函數以下:
class Test { public static string x = EchoAndReturn("In type initializer"); static Test() { } public static string EchoAndReturn(string s) { Console.WriteLine(s); return s; } }
圖7 輸出結果
理論上,type initializer應該發生在」Echo!」以後和」After echo」以前,但這裏卻出現了不惟一的結果,只有當Test類包含靜態構造函數時,才能確保type initializer的初始化發生在」Echo!」以後和」After echo」以前。
因此說要確保type initializer發生在被字段引用時,咱們應該給該類添加靜態構造函數。接下來讓咱們介紹單例模式的靜態方式。
public sealed class Singleton { private static readonly Singleton _instance = new Singleton(); // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Singleton() { } /// <summary> /// Prevents a default instance of the /// <see cref="Singleton"/> class from being created. /// </summary> private Singleton() { } /// <summary> /// Gets the instance. /// </summary> public static Singleton Instance { get { return _instance; } } }
以上方式實現比以前介紹的方式都要簡單,但它確實是多線程環境下,C#實現的Singleton的一種方式。因爲這種靜態初始化的方式是在本身的字段被引用時纔會實例化。
讓咱們經過IL代碼來分析靜態初始化。
圖8靜態初始化IL代碼
首先這裏沒有beforefieldinit的修飾符,因爲咱們添加了靜態構造函數當靜態字段被引用時才進行初始化,所以即使不少線程試圖引用_instance,也須要等靜態構造函數執行完並把靜態成員_instance實例化以後可使用。
/// <summary> /// Delaies initialization. /// </summary> public sealed class Singleton { private Singleton() { } /// <summary> /// Gets the instance. /// </summary> public static Singleton Instance { get { return Nested._instance; } } private class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly Singleton _instance = new Singleton(); } }
這裏咱們把初始化工做放到Nested類中的一個靜態成員來完成,這樣就實現了延遲初始化。
/// <summary> /// .NET 4's Lazy<T> type /// </summary> public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() { } }
這種方式的簡單和性能良好,並且還提供檢查是否已經建立實例的屬性IsValueCreated。
如今讓咱們使用單例模式(Singleton)實現負載平衡器,首先咱們定義一個服務器類,它包含服務器名和IP地址以下:
/// <summary> /// Represents a server machine /// </summary> class Server { // Gets or sets server name public string Name { get; set; } // Gets or sets server IP address public string IP { get; set; } }
因爲負載平衡器只提供一個對象實例供服務器使用,因此咱們使用單例模式(Singleton)實現該負載平衡器。
/// <summary> /// The 'Singleton' class /// </summary> sealed class LoadBalancer { private static readonly LoadBalancer _instance = new LoadBalancer(); // Type-safe generic list of servers private List<Server> _servers; private Random _random = new Random(); static LoadBalancer() { } // Note: constructor is 'private' private LoadBalancer() { // Load list of available servers _servers = new List<Server> { new Server{ Name = "ServerI", IP = "192.168.0.108" }, new Server{ Name = "ServerII", IP = "192.168.0.109" }, new Server{ Name = "ServerIII", IP = "192.168.0.110" }, new Server{ Name = "ServerIV", IP = "192.168.0.111" }, new Server{ Name = "ServerV", IP = "192.168.0.112" }, }; } /// <summary> /// Gets the instance through static initialization. /// </summary> public static LoadBalancer Instance { get { return _instance; } } // Simple, but effective load balancer public Server NextServer { get { int r = _random.Next(_servers.Count); return _servers[r]; } } }
static void Main() { LoadBalancer b1 = LoadBalancer.Instance; b1.GetHashCode(); LoadBalancer b2 = LoadBalancer.Instance; LoadBalancer b3 = LoadBalancer.Instance; LoadBalancer b4 = LoadBalancer.Instance; // Confirm these are the same instance if (b1 == b2 && b2 == b3 && b3 == b4) { Console.WriteLine("Same instance\n"); } // Next, load balance 15 requests for a server LoadBalancer balancer = LoadBalancer.Instance; for (int i = 0; i < 15; i++) { string serverName = balancer.NextServer.Name; Console.WriteLine("Dispatch request to: " + serverName); } Console.ReadKey(); }
圖9 LoadBalancer輸出結果
單例模式的優勢:
單例模式(Singleton)會控制其實例對象的數量,從而確保訪問對象的惟一性。
單例模式的缺點:
單例適用性
使用Singleton模式有一個必要條件:在一個系統要求一個類只有一個實例時才應當使用單例模式。反之,若是一個類能夠有幾個實例共存,就不要使用單例模式。
不要使用單例模式存取全局變量。這違背了單例模式的用意,最好放到對應類的靜態成員中。
不要將數據庫鏈接作成單例,由於一個系統可能會與數據庫有多個鏈接,而且在有鏈接池的狀況下,應當儘量及時釋放鏈接。Singleton模式因爲使用靜態成員存儲類實例,因此可能會形成資源沒法及時釋放,帶來問題。
========================================================