C#設(shè)計(jì)模式之Singleton模式
前言
Singleton是二十三個(gè)設(shè)計(jì)模式中比較重要也比較經(jīng)常使用的模式。但是這個(gè)模式雖然簡單,實(shí)現(xiàn)起來也會(huì)有一些小坑,讓我們一起來看看吧!
實(shí)現(xiàn)思路
首先我們看看這個(gè)設(shè)計(jì)模式的UML類圖。
很清晰的可以看到,有三點(diǎn)是需要我們?cè)趯?shí)現(xiàn)這個(gè)模式的時(shí)候注意的地方。
- 私有化的構(gòu)造器
- 全局唯一的靜態(tài)實(shí)例
- 能夠返回全局唯一靜態(tài)實(shí)例的靜態(tài)方法
其中,私有化構(gòu)造器是防止外部用戶創(chuàng)建新的實(shí)例而靜態(tài)方法用于返回全局唯一的靜態(tài)實(shí)例供用戶使用。原理清楚了,接下來我們看看一些典型的實(shí)現(xiàn)方式和其中的暗坑。
實(shí)現(xiàn)方法
最簡單的實(shí)現(xiàn)方法
最簡單的實(shí)現(xiàn)方法自然就是按照UML類圖直接寫一個(gè)類,我們看看代碼。
class Program { static void Main(string[] args) { var single1 = Singleton.Instance; var single2 = Singleton.Instance; Console.WriteLine(object.ReferenceEquals(single1, single2)); Console.ReadLine(); } } class Singleton { private static Singleton _Instance = null; private Singleton() { Console.WriteLine("Created"); } public static Singleton Instance { get { if (_Instance == null) { _Instance = new Singleton(); } return _Instance; } } public void DumbMethod() { } }
這段代碼忠實(shí)的實(shí)現(xiàn)了UML類圖里面的一切,查看輸出結(jié)果,
證實(shí)了Singleton確實(shí)起了作用,多次調(diào)用僅僅產(chǎn)生了一個(gè)實(shí)例,似乎這么寫就可以實(shí)現(xiàn)這個(gè)模式了。但是,真的會(huì)那么簡單嗎?
如果多線程亂入?
現(xiàn)在我們給剛剛的例子加點(diǎn)調(diào)料,假設(shè)多個(gè)對(duì)實(shí)例的調(diào)用,并不是簡單的,彬彬有禮的順序關(guān)系,二是以多線程的方式調(diào)用,那么剛剛那種實(shí)現(xiàn)方法,還能從容應(yīng)對(duì)嗎?讓我們?cè)囋?。把Main函數(shù)里面的調(diào)用改成這樣。
static void Main(string[] args) { int TOTAL = 10000; Task[] tasks = new Task[TOTAL]; for (int i = 0; i < TOTAL; i++) { tasks[i] = Task.Factory.StartNew(() => { Singleton.Instance.DumbMethod(); }); } Task.WaitAll(tasks); Console.ReadLine(); }
通過Factory創(chuàng)造出1萬個(gè)Task,幾乎同時(shí)去請(qǐng)求這個(gè)單例,看看輸出。
咦,我們剛剛寫的Singleton模式失效了,這個(gè)類被創(chuàng)造了5次(這段代碼運(yùn)行多次,這個(gè)數(shù)字不一定相同),一定是多線程搞的鬼,我們剛剛寫的代碼沒有辦法應(yīng)對(duì)多線程,換句話說,是非線程安全的(thread-safe),那有沒有辦法來攻克這個(gè)難關(guān)呢?
線程安全的單例模式
Lock版本
提到線程安全,很多同學(xué)第一反應(yīng)就是用lock,不錯(cuò),lock是個(gè)可行的辦法,讓我們?cè)囋?。添加一個(gè)引用類型的對(duì)象作為lock對(duì)象,修改代碼如下(什么?你問我為什必須是引用類型的對(duì)象而不能是值類型的對(duì)象?因?yàn)閘ock的時(shí)候,如果對(duì)象是值類型,那么lock僅僅鎖住了它的一個(gè)副本,另外一個(gè)線程可以暢通無阻的再次lock,這樣lock就失去了阻塞線程的意義)
private static object _SyncObj = new object(); public static Singleton Instance { get { lock (_SyncObj) { if (_Instance == null) { _Instance = new Singleton(); } return _Instance; } } }
運(yùn)行一下,輸出
只有一個(gè)實(shí)例創(chuàng)建,證明Lock起作用了,這個(gè)模式可行!不過有些不喜歡用Lock的同學(xué)可能要問,還有沒有其他辦法呢?答案是有的。
靜態(tài)構(gòu)造器版本
回想一下,C#中的類靜態(tài)構(gòu)造器,只會(huì)在這個(gè)類第一次被使用的時(shí)候調(diào)用一次,天然的線程安全,那我們?cè)囋嚥挥肔ock使用類靜態(tài)構(gòu)造器?修改Singleton類如下:
class Singleton { private static Singleton _Instance = null; private Singleton() { Console.WriteLine("Created"); } static Singleton() { _Instance = new Singleton(); } //private static object _SyncObj = new object(); public static Singleton Instance { get { return _Instance; } } public void DumbMethod() { } }
去掉了Lock,添加了一個(gè)類靜態(tài)構(gòu)造器,試一試。
完美!對(duì)于不喜歡用Lock(在這個(gè)例子中,實(shí)例只會(huì)創(chuàng)建一次但是之后的所有線程都要先排隊(duì)Lock再進(jìn)入Critical code進(jìn)行檢查,效率比較低下)的同學(xué),類靜態(tài)構(gòu)造器提供了一種很好的選擇。
不過俗話說,人心苦不足 , 我們總是追求卓越。這個(gè)版本比Lock版本似乎更好一點(diǎn),那還有沒有更好的版本呢?有的。
Lazy版本
從net 4.0開始,C#開始支持延遲初始化,通過Lazy關(guān)鍵字,我們可以聲明某個(gè)對(duì)象為僅僅當(dāng)?shù)谝淮问褂玫臅r(shí)候,再初始化,如果一直沒有調(diào)用,那就不初始化,省去了一部分不必要的開銷,提升了效率。如果你不熟悉Lazy或者想更多了解它,請(qǐng)參考。我們今天關(guān)注的重點(diǎn)在于,Lazy也是天生線程安全的,所以我們嘗試用它來實(shí)現(xiàn)Singleton模式?修改代碼如下:
class Singleton { private static Lazy<Singleton> _Instance = new Lazy<Singleton>(() => new Singleton()); private Singleton() { Console.WriteLine("Created"); } public static Singleton Instance { get { return _Instance.Value; } } public void DumbMethod() { } }
輸出結(jié)果中可以看到,我們達(dá)到了想要的效果:
在上面的代碼中,私有變量_Instance現(xiàn)在是被聲明為延遲初始化,這樣不但天然實(shí)現(xiàn)了線程安全,同時(shí)在沒有調(diào)用Instance靜態(tài)方法的時(shí)候(也即沒有調(diào)用_Instance.Value),初始化不會(huì)發(fā)生,這樣就提高了效率。
總結(jié)
Singleton模式很常見,實(shí)現(xiàn)起來也很簡單,只是要小心線程安全。以上三種方法都可以實(shí)現(xiàn)線程安全的Singleton模式。如果net 版本在4.0之上,建議使用Lazy版本,畢竟對(duì)比Lock版本,Lazy版本可以免去實(shí)現(xiàn)手動(dòng)Lock之苦,對(duì)比Static版本,又有延遲初始化的性能優(yōu)勢,何樂而不為呢?
以上就是C#設(shè)計(jì)模式之Singleton模式的詳細(xì)內(nèi)容,更多關(guān)于C#中的Singleton模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#中派生類調(diào)用基類構(gòu)造函數(shù)用法分析
這篇文章主要介紹了C#中派生類調(diào)用基類構(gòu)造函數(shù)用法,實(shí)例分析了派生類調(diào)用基類構(gòu)造函數(shù)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04C#使用Twain協(xié)議實(shí)現(xiàn)掃描儀連續(xù)掃描功能
這篇文章主要介紹了C#使用Twain協(xié)議實(shí)現(xiàn)掃描儀連續(xù)掃描,只需一行代碼,就可實(shí)現(xiàn)一次掃描多張,且不需要更改掃描儀的任何設(shè)置,需要的朋友可以參考下2022-01-01C#中參數(shù)數(shù)組、引用參數(shù)和輸出參數(shù)示例詳解
這篇文章主要給大家介紹了關(guān)于C#中參數(shù)數(shù)組、引用參數(shù)和輸出參數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05c# 使用模式匹配以及 is 和 as 運(yùn)算符安全地進(jìn)行強(qiáng)制轉(zhuǎn)換
這篇文章主要介紹了c# 使用模式匹配以及 is 和 as 運(yùn)算符安全地進(jìn)行強(qiáng)制轉(zhuǎn)換,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下2020-10-10C#檢測移動(dòng)硬盤并獲取移動(dòng)硬盤盤符的方法
這篇文章主要介紹了利用C#檢測移動(dòng)硬盤并獲取移動(dòng)硬盤盤符2017-12-12C#實(shí)現(xiàn)計(jì)算兩個(gè)坐標(biāo)點(diǎn)直接距離的方法小結(jié)
這篇文章主要為大家詳細(xì)介紹了C#中幾種常見場景下兩個(gè)坐標(biāo)點(diǎn)直接距離的計(jì)算方法,文中的示例代碼講解詳細(xì),有需要的可以參考一下2024-04-04C# Winform 實(shí)現(xiàn)控件自適應(yīng)父容器大小的示例代碼
這篇文章主要介紹了C# Winform 實(shí)現(xiàn)控件自適應(yīng)父容器大小的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03C#控制臺(tái)程序中處理2個(gè)關(guān)閉事件的代碼實(shí)例
這篇文章主要介紹了C#控制臺(tái)程序中處理2個(gè)關(guān)閉事件的代碼實(shí)例,本文中的2個(gè)關(guān)閉事件是指Ctrl+C事件和窗口的關(guān)閉按鈕事件,需要的朋友可以參考下2014-09-09