1. 單件模式簡介
1.1 定義
單件模式(Singleton)定義:要求一個類有且僅有一個實例,並且提供了一個全局的訪問點,在同一時刻只能被一個線程所訪問。
單件模式的特點:
(1)單件類只能有一個實例。
(2)單件類必須自身創建唯一實例。
(3)單件類必須給所有其它對象提供唯一實例。
1.2 使用頻率
中高
2、單件模式結構
2.1 結構圖

2.2 參與者
單件模式參與者:
◊ Singleton
° 被調用的單件對象;
° 在單件模式中,通常由Instance()或GetInstance()方法負責對象的創建,該方法應保證每個需要(單件)對象的客戶端均能訪問。
3. 單件模式結構實現
3.1 單件模式實現要點
◊ 單件類有一個私有的無參構造函數,防止被其他類實例化。
◊ 單件類不能被繼承,使用sealed修飾。
◊ 單件類使用靜態的變量保存單實例的引用。
◊ 單件類使用公有靜態方法獲取單一實例的引用,如果實例為null即創建一個。
3.2 C#代碼

(1)非線程安全
主要實現原理:在不考慮並發的情況下,通過以下2點實現單一實例。
(a) 靜態變量和靜態方法在內存中唯一。
(b) 私有構造函數確保不能通過調用構造函數來生成實例。
Singleton.cs
using System; using System.Collections.Generic; using System.Text; namespace Libing.DesignPatterns.SingletonPattern.Structural { /// <summary> /// 單件模式實現方式:由於該實現方式非線程安全,在實際應用中不推薦使用。 /// </summary> public sealed class Singleton { // 定義一個靜態變量來保存類的實例 private static Singleton _instance; // 私有構造函數,防止通過new實例化對象 private Singleton() { } /// <summary> /// 定義公有靜態方法,獲取實例,並加入判斷,保證實例只被創建一次 /// </summary> /// <returns></returns> public static Singleton Instance() { // 使用延遲初始化 // 若類的實例不存在則創建實例,若存在則返回實例 // 注: 非線程安全 if (_instance == null) { _instance = new Singleton(); } return _instance; } } }
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Libing.DesignPatterns.SingletonPattern.Structural; namespace Libing.DesignPatterns.SingletonPattern { class Program { static void Main(string[] args) { // 創建一個實例s1 Singleton s1 = Singleton.Instance(); // 創建一個實例s2 Singleton s2 = Singleton.Instance(); if (s1 == s2) { Console.WriteLine("對象為相同實例"); } } } }
運行輸出:
對象為相同實例
請按任意鍵繼續. . .
注:以上的實現方式適用於單線程環境,在多線程的環境下有可能得到Singleton類的多個實例。假如同時有兩個線程去判斷(null == _singleton),並且得到的結果為真,那么兩個線程都會創建類Singleton的實例,這樣就違背了Singleton模式“唯一實例”的原則。
多線程測試:
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace Libing.DesignPatterns.SingletonPattern.Structural { /// <summary> /// 單件模式實現方式:由於該實現方式非線程安全,在實際應用中不推薦使用。 /// </summary> public sealed class Singleton { // 定義一個靜態變量來保存類的實例 private static Singleton _instance; // 私有構造函數,防止通過new實例化對象 private Singleton() { } /// <summary> /// 定義公有靜態方法,獲取實例,並加入判斷,保證實例只被創建一次 /// </summary> /// <returns></returns> public static Singleton Instance() { // 使用延遲初始化 // 若類的實例不存在則創建實例,若存在則返回實例 // 注: 非線程安全 if (_instance == null) { Thread.Sleep(1000); // 模擬線程阻塞 _instance = new Singleton(); } return _instance; } } }
using System; using System.Threading; using Libing.DesignPatterns.SingletonPattern.Structural; namespace Libing.DesignPatterns.SingletonPattern { class Program { static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(Display)); t1.Start(); Thread t2 = new Thread(new ThreadStart(Display)); t2.Start(); } public static void Display() { Singleton s = Singleton.Instance(); Console.WriteLine("Singleton:" + s.GetHashCode()); } } }
運行結果:
Singleton:63835064 Singleton:6044116
(2)簡單線程安全
using System; using System.Collections.Generic; using System.Text; namespace Libing.DesignPatterns.SingletonPattern.Structural { /// <summary> /// 單件模式實現方式:簡單線程安全。 /// </summary> public sealed class Singleton { // 定義一個靜態變量來保存類的實例 private static Singleton _instance; // 定義一個標識確保線程同步 private static readonly object _syncLock = new object(); // 私有構造函數,防止通過new實例化對象 private Singleton() { } /// <summary> /// 定義公有靜態方法,獲取實例,並加入判斷,保證實例只被創建一次 /// </summary> /// <returns></returns> public static Singleton Instance() { // 當第一個線程運行到這里時,此時會對_syncLock對象 "加鎖", // 當第二個線程運行該方法時,首先檢測到_syncLock對象為"加鎖"狀態,該線程就會掛起等待第一個線程解鎖 // lock語句運行完之后(即線程運行完之后)會對該對象"解鎖" lock (_syncLock) { // 使用延遲初始化 // 若類的實例不存在則創建實例,若存在則返回實例 if (_instance == null) { _instance = new Singleton(); } } return _instance; } } }
以上方式的實現方式是線程安全的,首先創建了一個靜態只讀的進程輔助對象,由於lock是確保當一個線程位於代碼的臨界區時,另一個線程不能進入臨界區(同步操作)。如果其他線程試圖進入鎖定的代碼,則它將一直等待,直到該對象被釋放。從而確保在多線程下不會創建多個對象實例了。
但這種實現方式要進行同步操作,將影響系統性能的瓶頸和增加了額外的開銷。
(3)雙重鎖定線程安全
在上面簡單線程安全代碼中,對於每個線程都會對線程輔助對象locker加鎖之后再判斷實例是否存在。
對於這個操作完全沒有必要的,因為當第一個線程創建了該類的實例之后,后面的線程此時只需要直接判斷(uniqueInstance==null)為假,而不必要對線程輔助對象加鎖之后再去判斷,所以上面的實現方式增加了額外的開銷,損失了性能。
為了改進上面實現方式的缺陷,只需要在lock語句前面加一句(uniqueInstance==null)的判斷即可避免鎖所增加的額外開銷,這種實現方式稱為“雙重鎖定”。
using System; using System.Collections.Generic; using System.Text; namespace Libing.DesignPatterns.SingletonPattern.Structural { /// <summary> /// 單件模式實現方式:雙重鎖定線程安全。 /// </summary> public sealed class Singleton { // 定義一個靜態變量來保存類的實例 private static Singleton _instance; // 定義一個標識確保線程同步 private static readonly object _syncLock = new object(); // 私有構造函數,防止通過new實例化對象 private Singleton() { } /// <summary> /// 定義公有靜態方法,獲取實例,並加入判斷,保證實例只被創建一次 /// </summary> /// <returns></returns> public static Singleton Instance() { // 當第一個線程運行到這里時,此時會對_syncLock對象 "加鎖", // 當第二個線程運行該方法時,首先檢測到_syncLock對象為"加鎖"狀態,該線程就會掛起等待第一個線程解鎖 // lock語句運行完之后(即線程運行完之后)會對該對象"解鎖" // 雙重鎖定只需要一句判斷即可 if (_instance == null) { lock (_syncLock) { // 使用延遲初始化 // 若類的實例不存在則創建實例,若存在則返回實例 if (_instance == null) { _instance = new Singleton(); } } } return _instance; } } }
NUnit測試:
using System; using System.Collections.Generic; using System.Reflection; using System.Text; using Libing.DesignPatterns.SingletonPattern.Structural; using NUnit.Framework; namespace Libing.DesignPatterns.SingletonPattern.Tests { [TestFixture] public class SingletonTests { [Test] public void TestCreateSingleton() { Singleton s1 = Singleton.Instance(); Singleton s2 = Singleton.Instance(); Assert.AreSame(s1, s2); } [Test] public void TestNoPublicConstructors() { Type singleton = typeof(Singleton); ConstructorInfo[] ctrs = singleton.GetConstructors(); bool hasPublicConstructor = false; foreach (ConstructorInfo c in ctrs) { if (c.IsPublic) { hasPublicConstructor = true; break; } } Assert.IsFalse(hasPublicConstructor); } } }
Microsoft.VisualStudio.TestTools.UnitTesting測試:
using System; using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; using Libing.DesignPatterns.SingletonPattern.Structural; namespace Libing.DesignPatterns.SingletonPattern.Tests { [TestClass] public class SingletonTests { [TestMethod] public void TestCreateSingleton() { Singleton s1 = Singleton.Instance(); Singleton s2 = Singleton.Instance(); Assert.AreSame(s1, s2); } [TestMethod] public void TestNoPublicConstructors() { Type singleton = typeof(Singleton); ConstructorInfo[] ctrs = singleton.GetConstructors(); bool hasPublicConstructor = false; foreach (ConstructorInfo c in ctrs) { if (c.IsPublic) { hasPublicConstructor = true; break; } } Assert.IsFalse(hasPublicConstructor); } } }

4. 單件模式應用分析
4.1 單件模式使用注意點
(1)不要使用單例模式存取全局變量。這違背了單例模式的用意,最好放到對應類的靜態成員中。
(2)不要將數據庫連接做成單例,因為一個系統可能會與數據庫有多個連接,並且在有連接池的情況下,應當盡可能及時釋放連接。Singleton模式由於使用靜態成員存儲類實例,所以可能會造成資源無法及時釋放。
4.2 單件模式適用情形
(1)當類只能有一個實例而且客戶可以從一個眾所周知的訪問點訪問時;
(2)當這個唯一實例應該是通過子類化可擴展的,並且客戶應該無需更改代碼就能適用一個擴展的實例時。
