如何正确的使用单例模式
在最近的一個(gè)項(xiàng)目里面發(fā)現(xiàn)好多同事喜歡這樣運(yùn)用單例模式,樣例代碼如下
public class Demo { public static Demo Instance{ get { return new Demo(); }} public string GetUserId(){ return "001";} public string GetUserName(){ return "tauruswu";} }在調(diào)用這個(gè)類的時(shí)候,是這樣操作的
var id = Demo.Instance.GetUserId(); var name = Demo.Instance.GetUserName();粗略一看,可能覺得沒有問題,最開始我也是這樣,看別人都這么寫,我也就這么寫,其實(shí)這個(gè)時(shí)候你的直覺已經(jīng)明顯的欺騙你了,各位看官再仔細(xì)看看Demo類里面的靜態(tài)屬性Instance以及我們調(diào)用的方式,有沒有看出什么端倪來?
很顯然,上面的調(diào)用方法已經(jīng)違背了單例模式的宗旨,或者可以說是披著單例模式的外衣,卻不做單例模式該做的事情。單例模式的解釋是:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。那么我們應(yīng)該如何正確的使用單例模式了?
何為單例模式
再回頭看上面解釋單例模式的話,第一句話說“保證一個(gè)類僅有一個(gè)實(shí)例”,好,那么我們?cè)鯓幽軌虮WC一個(gè)類僅有一個(gè)實(shí)例了,幸好在C#里面,提供了私有構(gòu)造器,我們?cè)趧?chuàng)建一個(gè)類的時(shí)候,往往會(huì)在類的構(gòu)造函數(shù)里面初始化一些對(duì)象,這里的構(gòu)造器是公開的,如下面
public Demo(){// to do }那么很顯然,私有構(gòu)造器就是private的,如下面
private Demo(){// to do }一旦有了私有構(gòu)造器,那么這個(gè)類就會(huì)阻止類外面的代碼創(chuàng)建實(shí)例,不相信我們就來嘗試一下。還是用上面的Demo類
public class Demo { private Demo(){ // to do } }然后再外面去實(shí)例化它,看截圖
?這樣一來,你應(yīng)該明白了私有構(gòu)造器的作用了吧。
解釋單例模式的前半句話上面說的很清楚了,然后在看看后半句“并提供一個(gè)訪問它的全局訪問點(diǎn)”,也就是說向外部提供訪問該實(shí)例的方法或者屬性,怎么寫?我們將開篇的例子稍作修改
public class Demo {private Demo(){ // to do } private static Demo _instance; public static Demo Instance{ get{if (_instance == null){
Console.WriteLine(string.Format("線程{0}在{1}時(shí)刻發(fā)現(xiàn)Instance為null", Thread.CurrentThread.Name,DateTime.Now));_instance = new Demo();
}return _instance;}} }
調(diào)用方式與開篇的一樣,這個(gè)時(shí)候你在單步調(diào)試進(jìn)去,看看發(fā)現(xiàn)了什么。到這里,我們因該能正確的理解單例模式以及如何使用單例模式了。
多線程環(huán)境下莫名其妙的錯(cuò)誤
上面的例子在單線程環(huán)境下可以正常的運(yùn)轉(zhuǎn),如果換做是多線程環(huán)境下,它還能正確的運(yùn)轉(zhuǎn)嗎?
我們來做這樣一個(gè)實(shí)驗(yàn):1. 在一個(gè)程序啟動(dòng)時(shí)創(chuàng)建兩個(gè)線程,線程A與線程B
2. 線程A與線程B分別調(diào)用Demo類
如果僅憑自覺的話,我們肯定會(huì)覺得只有一個(gè)線程來創(chuàng)建Demo的實(shí)例,那么事實(shí)是不是這樣了?Demo類還是上面的那個(gè)類,未作任何修改。然后在另一個(gè)類中啟動(dòng)兩個(gè)線程,分別調(diào)用類Demo
public class Invoke{public void Run(){Thread t1 = new Thread(new ThreadStart(fun1));t1.Name = "AAA";t1.Start();Thread t2 = new Thread(new ThreadStart(fun2));t2.Name = "BBB";t2.Start();}private void fun1(){while (true){Demo1.Instance.GetUserId();Thread.Sleep(1);}}private void fun2(){while (true){Demo1.Instance.GetUserId();Thread.Sleep(1);}}}最后我們?cè)诳刂婆_(tái)程序里面運(yùn)行調(diào)用類Singleton,看效果圖
哥,你目瞪口呆了吧?怎么會(huì)有這樣的結(jié)果?事實(shí)證明上面的寫法在多線程環(huán)境里面會(huì)出問題的,那么我們?cè)撛趺礃尤バ薷乃?#xff0c;讓它能在多線程環(huán)境下正確的運(yùn)行。
如何修正在多線程環(huán)境下的bug
這里我們會(huì)用到著名的雙檢鎖技術(shù),英文名就是“Double-Check Locking”,它是線程同步機(jī)制中的一種,它背后的思路是,如果對(duì)象已經(jīng)構(gòu)造好,就不需要線程同步,另外如果調(diào)用如上面提到的屬性“Instance”的線程A發(fā)現(xiàn)對(duì)象沒有創(chuàng)建好,就會(huì)獲取一個(gè)線程同步鎖來確保只有一個(gè)線程構(gòu)造單例對(duì)象,基于這,我們將Demo類再稍微調(diào)整下
public class Demo1{private Demo1(){// to do }private static Demo1 _lock = new Demo1();private static Demo1 _instance;public static Demo1 Instance{get{if (_instance != null) return _instance;Monitor.Enter(_lock);if (_instance == null){Console.WriteLine(string.Format("線程{0}在{1}時(shí)刻發(fā)現(xiàn)Instance為null", Thread.CurrentThread.Name, DateTime.Now));_instance = new Demo1();}Monitor.Exit(_lock);return _instance;}}}然后在調(diào)用類中再啟動(dòng)多兩個(gè)線程CCC,DDD,再次啟動(dòng)程序
這次的結(jié)果表明只有一個(gè)線程創(chuàng)建了Demo類的實(shí)例了。其實(shí)上面的寫法不是很嚴(yán)謹(jǐn)?shù)?#xff0c;就是當(dāng)私有構(gòu)造器未執(zhí)行完,其他的線程已經(jīng)發(fā)現(xiàn)Instance不為null了,不過這個(gè)問題很難模擬出來。未了解決這種問題,那么就要用到Interlocked.Exchange() 這個(gè)方法。
還有其他方式創(chuàng)建單例嗎
除了雙檢索技術(shù),還有其他方式實(shí)現(xiàn)單例模式嗎?答案是肯定的。先來看些下面這種方式
public class Demo2{private static Demo2 _demo2 = new Demo2();private Demo2(){Console.WriteLine(string.Format("線程{0}在{1}時(shí)刻執(zhí)行私有構(gòu)造函數(shù)", Thread.CurrentThread.Name, DateTime.Now));}public static Demo2 Instance{get { return _demo2; }}}?
在看下執(zhí)行結(jié)果圖
?
那么它的原理是什么了?這里涉及到類型構(gòu)造器了,由于當(dāng)代碼首次訪問類的一個(gè)成員時(shí),CLR 會(huì)自動(dòng)調(diào)用一個(gè)類型的類構(gòu)造器,所以當(dāng)有一個(gè)線程訪問屬性Instance的時(shí)候,CLR會(huì)自動(dòng)調(diào)用類構(gòu)造器,從而創(chuàng)建這個(gè)對(duì)象的實(shí)例。
總結(jié)
這個(gè)話題已經(jīng)被寫亂了,如果我之前不仔細(xì)看項(xiàng)目里面的代碼,我也不會(huì)發(fā)現(xiàn)這個(gè)問題,有些時(shí)候總是會(huì)被感覺所欺騙,所以最好的方法就是自己動(dòng)手親自實(shí)踐一番,無非就是幾個(gè)小時(shí)的事情而已。那么你看完這篇文章之后有沒有什么感想了?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/wucj/p/3157294.html
總結(jié)
以上是生活随笔為你收集整理的如何正确的使用单例模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 看到南京饿死两个女竟的报道
- 下一篇: Oracle技术之OCRCONFIG工具