網(wǎng)站首頁 編程語言 正文
介紹
單例模式是軟件工程學中最富盛名的設計模式之一。從本質(zhì)上看,單例模式只允許被其自身實例化一次,且向外部提供了一個訪問該實例的接口。通常來說,單例對象進行實例化時一般不帶參數(shù),因為如果不同的實例化請求傳遞的參數(shù)不同的話會導致問題的產(chǎn)生。(若多個請求都是傳遞的同樣的參數(shù)的話,工廠模式更應該被考慮)
C#中實現(xiàn)單例有很多種方法,本文將按順序介紹非線程安全、完全懶漢式、線程安全和低/高性能集中版本。
在所有的實現(xiàn)版本中,都有以下幾個共同點:
唯一的、私有的且無參的構造函數(shù),這樣不允許外部類進行實例化;
類是密封的,盡管這不是強制的,但是嚴格來講從上一點來看密封類能有助于JIT的優(yōu)化;
一個靜態(tài)變量應該指向類的唯一實例;
一個公共的靜態(tài)變量用于獲得這個類的唯一實例(如果需要,應該創(chuàng)建它);
需要注意的是,本文中所有的例子中都是用一個 public static Instance的變量來訪問單例類實例,要將其轉(zhuǎn)換成公共函數(shù)是很容易的,但是這樣并不會帶來效率和線程安全上的提升。
Version 1 - 非線程安全
public sealed class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if (instance == null) { instance = new Singleton(); } return instance; } } }
該版本在多線程下是不安全的,會創(chuàng)建多個實例,請不要在生產(chǎn)環(huán)境中使用!
因為如果兩個線程同時運行到if(instance==null)判斷時,就會創(chuàng)建兩個實例,這是違背單例模式的初衷的。實際上在后面那個線程進行判斷是已經(jīng)生成了一個實例,但是對于不同的線程來說除非進行了線程間的通信,否則它是不知道的。
Version 2 - 簡單的線程安全
public sealed class Singleton2 { private static Singleton2 instance = null; private static readonly object obj = new object(); private Singleton2() { } public Singleton2 Instance { get { lock (obj) { if (instance == null) { instance = new Singleton2(); } return instance; } } } }
該版本是線程安全的。通過對一個過線程共享的對象進行加鎖操作,保證了在同一時刻只有一個線程在執(zhí)行l(wèi)ock{}里的代碼。當?shù)谝粋€線程在進行instance判斷或創(chuàng)建時,后續(xù)線程必須等待直到前一線程執(zhí)行完畢,因此保證了只有第一個線程能夠創(chuàng)建instance實例。
但不幸的是,因為每次對instance的請求都會進行l(wèi)ock操作,其性能是不佳的。
需要注意的是,這里使用了一個private static object變量進行鎖定,這是因為當如果對一個外部類可以訪問的對象進行鎖定時會導致性能低下甚至死鎖。因此通常來說為了保證線程安全,進行加鎖的對象應該是private的。
Version 3 - Double-check locking的線程安全
public sealed class Singleton3 { private static Singleton3 instance = null; private static object obj = new object(); private Singleton3() { } public static Singleton3 Instance { get { if (instance == null) { lock (obj) { if (instance == null) { instance = new Singleton3(); } } } return instance; } } }
該版本中試圖去避免每次訪問都進行加鎖操作并實現(xiàn)線程安全。然后,這段代碼對Java不起作用,因Java的內(nèi)存模型不能保證在構造函數(shù)一定在其他對象引用instance之前完成。還有重要的一點,它不如后面的實現(xiàn)方式。
Version 4 - 不完全懶漢式,但不加鎖的線程安全
public sealed class Singleton4 { private static readonly Singleton4 instance = new Singleton4(); /// <summary> /// 顯式的靜態(tài)構造函數(shù)用來告訴C#編譯器在其內(nèi)容實例化之前不要標記其類型 /// </summary> static Singleton4() { } private Singleton4() { } public static Singleton4 Instance { get { return instance; } } }
這個版本是的實現(xiàn)非常的簡單,但是卻又是線程安全的。C#的靜態(tài)構造函數(shù)只有在當其類的實例被創(chuàng)建或者有靜態(tài)成員被引用時執(zhí)行,在整個應用程序域中只會被執(zhí)行一次。使用當前方式明顯比前面版本中進行額外的判斷要快。
當然這個版本也存在一些瑕疵:
不是真正意義上的懶漢模式(需要的時候才創(chuàng)建實例),若單例類還存在其他靜態(tài)成員,當其他類第一次引用這些成員時便會創(chuàng)建該instance。下個版本實現(xiàn)會修正這個問題;
只有.NET中才具有beforefieldinit特性,即懶漢式實現(xiàn)。且在.Net 1.1以前的編譯器不支持,不過這個現(xiàn)在來看問題不大;
所有版本中,只有這里將instance設置成了readonly,這不僅保證了代碼的高校且顯得十分短小。
Version 5 - 完全懶漢實例化
public sealed class Singleton5 { private Singleton5() { } public static Singleton5 Instance { get { return Nested.instance; } } private class Nested { static Nested() { } internal static readonly Singleton5 instance = new Singleton5(); } }
該版本看起來稍微復雜難懂,其實只是在寫法上實現(xiàn)了上一版本的瑕疵,通過內(nèi)嵌類的方式先實現(xiàn)了只有在真正應用Instance時才進行實例化。其性能表現(xiàn)與上一版本無異。
Version 6 - 使用.NET 4 Lazy?type 特性
public sealed class Singleton6 { private static readonly Lazy<Singleton6> lazy = new Lazy<Singleton6>(()=> new Singleton6()); public static Singleton6 Instance { get { return lazy.Value; } } private Singleton6() { } }
如果你使用的是.NET 4或其以上版本,可以使用System.Lazy?type來實現(xiàn)完全懶漢式。其代碼看起來也很簡潔且性能表現(xiàn)也很好。
性能 VS 懶漢式
一般情況下,我們并不需要實現(xiàn)完全懶漢式,除非你的構造初始化執(zhí)行了某些費時的工作。因此一般的,我們使用顯式的靜態(tài)構造函數(shù)就能夠適用。
本文翻譯自Implementing the Singleton Pattern in C#
Exception
有時候在進行構造函數(shù)初始化時可能 會拋出異常,但這對整個應用程序來說不應該是致命的,所以可能的情況下,你應該自己處理這種異常情況。
總結(jié)
上述提供的幾種實現(xiàn)方法中,一般情況下提倡使用Version 4
,除非遇到有時早于單列類實例化時就引用了其他靜態(tài)成員。這種情況下,Version 2
一旦被考慮,雖然它看起來會因加鎖耗時,但是其實運行起來并沒有你想的那么慢,關鍵是你很容易寫對它。顯然Version 1
你永遠都不應該考慮,Version 3
在與Version 5
的對比下也是不在考慮范圍之內(nèi)的。
原文鏈接:https://www.cnblogs.com/zhaoshujie/p/14323654.html
相關推薦
- 2022-11-10 Android開發(fā)之AlertDialog實現(xiàn)彈出對話框_Android
- 2023-02-18 Flow轉(zhuǎn)LiveData數(shù)據(jù)丟失原理詳解_Android
- 2022-06-22 深入淺析C/C++?的條件編譯_C 語言
- 2022-09-22 Pod 生命周期與重啟策略
- 2022-11-10 Android傳感器的簡單使用方法_Android
- 2022-12-14 Flutter實現(xiàn)手勢識別功能詳解方法_Android
- 2023-07-30 element中對el-input 自定義驗證規(guī)則
- 2022-04-02 Python字符串與正則表達式詳細介紹_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支