設計模式學習-單例模式
餓漢模式
單例模式又被稱為單件模式,這個模式作用是保持程式中只有`唯一`物件,一聽到唯一,那肯定就明白了,無非就是不讓別人建立新物件唄,只需要兩點就可以
1.私有化建構函式, 2.建立一個靜態物件屬性以便外部使用
這麼簡單還算一種模式?別急,我們慢慢看,首先,我們建立一個單例
class Singleton { //靜態物件屬性 public static Singleton SingletonInstance { get; } = new Singleton(); //私有化建構函式 private Singleton() { Console.WriteLine("Singleton構造"); } }
挺簡單的嗎這不是,的確,挺簡單的,這就是單例模式其中之一的 餓漢模式
,什麼意思呢,
餓漢模式:在 程式啟動
或 單例類被載入
時,就例項化單例模式
但是這麼做不感覺有問題嗎?假如這個類我們並不使用或在程式啟動很久以後我們才使用,那麼這個物件的預建立不就很浪費嗎?並且如果這個物件的建立需要很大的資源,那....,所以我們需要延遲單例物件的建立.
懶漢模式
將物件延遲到第一次訪問時才建立,這種被稱為`懶漢模式`
懶漢模式:當第一次訪問單例物件時才去例項化物件
看起來也挺簡單的樣子,無非是將物件例項化放在屬性的 get
中
class Singleton { private static Singleton _singleton=null; //靜態物件屬性 public static Singleton SingletonInstance { get { if (_singleton == null) {//如果物件不存在則建立 _singleton = new Singleton(); } return _singleton; } } //私有化建構函式 private Singleton() { Console.WriteLine("Singleton構造"); } }
看起來感覺挺不錯的樣子,但是真是這樣嗎?來做一個多執行緒的例子看看.
static void Main(string[] args) { Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;}); Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;}); Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;}); Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;}); Console.ReadKey(); }
開4個執行緒進行測試
Task.Run() 是從執行緒池中獲取執行緒進行使用
可以看到對於多執行緒來說這個單例完全無用,解決多執行緒的辦法就是加鎖,所以需要在例項化物件進行加鎖
private static object objLock = new object(); //靜態物件屬性 public static Singleton SingletonInstance { get { lock (objLock) { if (_singleton == null) {//如果物件不存在則建立 _singleton = new Singleton(); } } return _singleton; } }
但是都知道,加鎖會極大的影響效能,每一次都判斷鎖感覺上是一種極大的浪費.,然後再次優化就出現了經典的單例例項實現 雙重檢查鎖
public static Singleton SingletonInstance { get { if (_singleton == null) {//解決鎖的效率問題. lock (objLock) { if (_singleton == null) {//如果物件不存在則建立 _singleton = new Singleton(); } } } return _singleton; } }
在外面加一個if判斷,這層if看起來是多餘的,但是它極大的節省了效能,杜絕了大多數無多併發時鎖的驗證,從而提高了效能.
C#單例另一種實現---延遲載入
在C#中有一個 Lazy 類,這個類是一個延遲載入類,也就是自動為我們實現延遲載入功能,並且還是執行緒安全的,也就是說完全可以利用這個類實現單例
class SingletonLazy { //Lazy需要一個無參有返的委託, public staticLazy<SingletonLazy> SingletonInstance = new Lazy<SingletonLazy>(() => { return new SingletonLazy(); }); //私有化建構函式 private SingletonLazy() { Console.WriteLine("SingletonLazy構造"); } }
Lazy構造器有一個引數是傳入一個無參有返的委託,整好可以例項化物件,
static void Main(string[] args) { SingletonLazy singletonLazy = null; Console.WriteLine("延遲"); Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; }); Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; }); Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; }); Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; }); Console.ReadKey(); }
對這個單例進行測試,測試結果與剛才無異,在工作中很多都是使用這種方式來實現單例模式
Lazy
下面來看看 Lazy 的實現機制,其實我們也大致能想到內部到底是如何處理的
public class Lazy<T> { //內部類,儲存資料的類 private class Boxed { internal T m_value; internal Boxed(T value) { m_value = value; } } private object m_boxed; //委託 private Func<T> m_valueFactory; //執行緒安全物件 private object m_threadSafeObj = new object(); //委託預設值 private static readonly Func<T> ALREADY_INVOKED_SENTINEL = () => default(T); public Lazy(Func<T> valueFactory) { m_valueFactory = valueFactory ?? throw new ArgumentNullException("valueFactory"); } //Value public T Value { get { Boxed boxed = null; if (m_boxed != null) { boxed = (m_boxed as Boxed); if (boxed != null) { return boxed.m_value; } throw new Exception(); } return LazyInitValue(); } } //初始化Value值 private T LazyInitValue() { Boxed boxed = null; //讀取安全物件 object obj = Volatile.Read<object>(ref m_threadSafeObj); bool flag = false; try { //如果是第一次,則開啟鎖並建立物件 if (ALREADY_INVOKED_SENTINEL !=obj) { Monitor.Enter(obj, ref flag); } if (m_boxed == null) { //建立Boxed物件並將m_threadSafeObj設定為ALREADY_INVOKED_SENTINEL boxed = (Boxed)(m_boxed = CreateValue()); Volatile.Write<object>(ref m_threadSafeObj, (object)ALREADY_INVOKED_SENTINEL); } else {//已存在物件 boxed = (m_boxed as Boxed); } } finally { if (flag) { Monitor.Exit(obj); } } return boxed.m_value; } //建立Boxed物件 private Boxed CreateValue() { if (m_valueFactory != null) { try { Func<T> valueFactory = m_valueFactory; if (valueFactory == ALREADY_INVOKED_SENTINEL) { return null; } return new Boxed(valueFactory()); } catch (Exception) { throw; } } try { //建立預設物件 return new Boxed((T)Activator.CreateInstance(typeof(T))); } catch (MissingMethodException) { throw; } } }
上面是簡化版的 Lazy 原始碼,可以看到 Lazy 也只是利用了一個內部類 Boxed 物件快取了資料,程式碼中有一點有意思的,在 LazyInitValue() 方法中使用了 Volatile 類讀取資料進行加鎖, volatile 是保持多執行緒下資料同步的問題,一種簡單方式可以在變數中加上volatile關鍵字
多個執行緒同時訪問一個變數時,CLR(Common Language Runtime)為了效率會進行相應優化,比如“允許執行緒進行本地快取”,這樣就可能導致變數訪問的不一致性。volatile就是為了解決這個問題;volatile修飾的變數,不允許執行緒進行本地快取,每個執行緒的讀寫都直接操作在共享記憶體上,這就保證了變數始終具有一致性
單例模式定義
單例模式保證在系統中一個類僅有一個例項物件