您好,登錄后才能下訂單哦!
C#單例模式的實(shí)現(xiàn)原理?這個(gè)問(wèn)題可能是我們?nèi)粘W(xué)習(xí)或工作經(jīng)常見(jiàn)到的。希望通過(guò)這個(gè)問(wèn)題能讓你收獲頗深。下面是小編給大家?guī)?lái)的參考內(nèi)容,讓我們一起來(lái)看看吧!
簡(jiǎn)介
單例指的是只能存在一個(gè)實(shí)例的類(在C#中,更準(zhǔn)確的說(shuō)法是在每個(gè)AppDomain之中只能存在一個(gè)實(shí)例的類,它是軟件工程中使用最多的幾種模式之一。在第一個(gè)使用者創(chuàng)建了這個(gè)類的實(shí)例之后,其后需要使用這個(gè)類的就只能使用之前創(chuàng)建的實(shí)例,無(wú)法再創(chuàng)建一個(gè)新的實(shí)例。通常情況下,單例會(huì)在第一次被使用時(shí)創(chuàng)建。本文會(huì)對(duì)C#中幾種單例的實(shí)現(xiàn)方式進(jìn)行介紹,并分析它們之間的線程安全性和性能差異。
單例的實(shí)現(xiàn)方式有很多種,但從最簡(jiǎn)單的實(shí)現(xiàn)(非延遲加載,非線程安全,效率低下),到可延遲加載,線程安全,且高效的實(shí)現(xiàn),它們都有一些基本的共同點(diǎn):
單例類都只有一個(gè)private的無(wú)參構(gòu)造函數(shù)
類聲明為sealed(不是必須的)
類中有一個(gè)靜態(tài)變量保存著所創(chuàng)建的實(shí)例的引用
單例類會(huì)提供一個(gè)靜態(tài)方法或?qū)傩詠?lái)返回創(chuàng)建的實(shí)例的引用(eg.GetInstance)
幾種實(shí)現(xiàn)
一非線程安全
//Bad code! Do not use! public sealed class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton instance { get { if (instance == null) { instance = new Singleton(); } return instance; } } }
這種方法不是線程安全的,會(huì)存在兩個(gè)線程同時(shí)執(zhí)行if (instance == null)并且創(chuàng)建兩個(gè)不同的instance,后創(chuàng)建的會(huì)替換掉新創(chuàng)建的,導(dǎo)致之前拿到的reference為空。
二簡(jiǎn)單的線程安全實(shí)現(xiàn)
public sealed class Singleton { private static Singleton instance = null; private static readonly object padlock = new object(); Singleton() { } public static Singleton Instance { get { lock (padlock) { if (instance == null) { instance = new Singleton(); } return instance; } } } }
相比較于實(shí)現(xiàn)一,這個(gè)版本加上了一個(gè)對(duì)instance的鎖,在調(diào)用instance之前要先對(duì)padlock上鎖,這樣就避免了實(shí)現(xiàn)一中的線程沖突,該實(shí)現(xiàn)自始至終只會(huì)創(chuàng)建一個(gè)instance了。但是,由于每次調(diào)用Instance都會(huì)使用到鎖,而調(diào)用鎖的開(kāi)銷較大,這個(gè)實(shí)現(xiàn)會(huì)有一定的性能損失。
注意這里我們使用的是新建一個(gè)private的object實(shí)例padlock來(lái)實(shí)現(xiàn)鎖操作,而不是直接對(duì)Singleton進(jìn)行上鎖。直接對(duì)類型上鎖會(huì)出現(xiàn)潛在的風(fēng)險(xiǎn),因?yàn)檫@個(gè)類型是public的,所以理論上它會(huì)在任何code里調(diào)用,直接對(duì)它上鎖會(huì)導(dǎo)致性能問(wèn)題,甚至?xí)霈F(xiàn)死鎖情況。
Note: C#中,同一個(gè)線程是可以對(duì)一個(gè)object進(jìn)行多次上鎖的,但是不同線程之間如果同時(shí)上鎖,就可能會(huì)出現(xiàn)線程等待,或者嚴(yán)重的會(huì)出現(xiàn)死鎖情況。因此,我們?cè)谑褂胠ock時(shí),盡量選擇類中的私有變量上鎖,這樣可以避免上述情況發(fā)生。
三雙重驗(yàn)證的線程安全實(shí)現(xiàn)
public sealed calss Singleton { private static Singleton instance = null; private static readonly object padlock = new object(); Singleton() { } public static Singleton Instance { get { if (instance == null) { lock (padlock) { if (instance == null) { instance = new Singleton(); } } } return instance; } } }
在保證線程安全的同時(shí),這個(gè)實(shí)現(xiàn)還避免了每次調(diào)用Instance都進(jìn)行l(wèi)ock操作,這會(huì)節(jié)約一定的時(shí)間。
但是,這種實(shí)現(xiàn)也有它的缺點(diǎn):
1無(wú)法在Java中工作。(具體原因可以見(jiàn)原文,這邊沒(méi)怎么理解)
2程序員在自己實(shí)現(xiàn)時(shí)很容易出錯(cuò)。如果對(duì)這個(gè)模式的代碼進(jìn)行自己的修改,要倍加小心,因?yàn)閐ouble check的邏輯較為復(fù)雜,很容易出現(xiàn)思考不周而出錯(cuò)的情況。
四不用鎖的線程安全實(shí)現(xiàn)
public sealed class Singleton { //在Singleton第一次被調(diào)用時(shí)會(huì)執(zhí)行instance的初始化 private static readonly Singleton instance = new Singleton(); //Explicit static consturctor to tell C# compiler //not to mark type as beforefieldinit static Singleton() { } private Singleton() { } public static Singleton Instance { get { return instance; } } }
這個(gè)實(shí)現(xiàn)很簡(jiǎn)單,并沒(méi)有用到鎖,但是它仍然是線程安全的。這里使用了一個(gè)static,readonly的Singleton實(shí)例,它會(huì)在Singleton第一次被調(diào)用的時(shí)候新建一個(gè)instance,這里新建時(shí)候的線程安全保障是由.NET直接控制的,我們可以認(rèn)為它是一個(gè)原子操作,并且在一個(gè)AppDomaing中它只會(huì)被創(chuàng)建一次。
這種實(shí)現(xiàn)也有一些缺點(diǎn):
1instance被創(chuàng)建的時(shí)機(jī)不明,任何對(duì)Singleton的調(diào)用都會(huì)提前創(chuàng)建instance
2static構(gòu)造函數(shù)的循環(huán)調(diào)用。如有A,B兩個(gè)類,A的靜態(tài)構(gòu)造函數(shù)中調(diào)用了B,而B(niǎo)的靜態(tài)構(gòu)造函數(shù)中又調(diào)用了A,這兩個(gè)就會(huì)形成一個(gè)循環(huán)調(diào)用,嚴(yán)重的會(huì)導(dǎo)致程序崩潰。
3我們需要手動(dòng)添加Singleton的靜態(tài)構(gòu)造函數(shù)來(lái)確保Singleton類型不會(huì)被自動(dòng)加上beforefieldinit這個(gè)Attribute,以此來(lái)確保instance會(huì)在第一次調(diào)用Singleton時(shí)才被創(chuàng)建。
4readonly的屬性無(wú)法在運(yùn)行時(shí)改變,如果我們需要在程序運(yùn)行時(shí)dispose這個(gè)instance再重新創(chuàng)建一個(gè)新的instance,這種實(shí)現(xiàn)方法就無(wú)法滿足。
五完全延遲加載實(shí)現(xiàn)(fully lazy instantiation)
public sealed class Singleton { private Singleton() { } public static Singleton Instance { get { return Nested.instance; } } private class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly Singleton instance = new Singleton(); } }
實(shí)現(xiàn)五是實(shí)現(xiàn)四的包裝。它確保了instance只會(huì)在Instance的get方法里面調(diào)用,且只會(huì)在第一次調(diào)用前初始化。它是實(shí)現(xiàn)四的確保延遲加載的版本。
六 使用.NET4的Lazy<T>類型
public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() { } }
.NET4或以上的版本支持Lazy<T>來(lái)實(shí)現(xiàn)延遲加載,它用最簡(jiǎn)潔的代碼保證了單例的線程安全和延遲加載特性。
性能差異
之前的實(shí)現(xiàn)中,我們都在強(qiáng)調(diào)代碼的線程安全性和延遲加載。然而在實(shí)際使用中,如果你的單例類的初始化不是一個(gè)很耗時(shí)的操作或者初始化順序不會(huì)導(dǎo)致bug,延遲初始化是一個(gè)可有可無(wú)的特性,因?yàn)槌跏蓟加玫臅r(shí)間是可以忽略不計(jì)的。
在實(shí)際使用場(chǎng)景中,如果你的單例實(shí)例會(huì)被頻繁得調(diào)用(如在一個(gè)循環(huán)中),那么為了保證線程安全而帶來(lái)的性能消耗是更值得關(guān)注的地方。
為了比較這幾種實(shí)現(xiàn)的性能,我做了一個(gè)小測(cè)試,循環(huán)拿這些實(shí)現(xiàn)中的單例9億次,每次調(diào)用instance的方法執(zhí)行一個(gè)count++操作,每隔一百萬(wàn)輸出一次,運(yùn)行環(huán)境是MBP上的Visual Studio for Mac。結(jié)果如下:
線程安全性 | 延遲加載 | 測(cè)試運(yùn)行時(shí)間(ms) | |
---|---|---|---|
實(shí)現(xiàn)一 | 否 | 是 | 15532 |
實(shí)現(xiàn)二 | 是 | 是 | 45803 |
實(shí)現(xiàn)三 | 是 | 是 | 15953 |
實(shí)現(xiàn)四 | 是 | 不完全 | 14572 |
實(shí)現(xiàn)五 | 是 | 是 | 14295 |
實(shí)現(xiàn)六 | 是 | 是 | 22875 |
測(cè)試方法并不嚴(yán)謹(jǐn),但是仍然可以看出,方法二由于每次都需要調(diào)用lock,是最耗時(shí)的,幾乎是其他幾個(gè)的三倍。排第二的則是使用.NET Lazy類型的實(shí)現(xiàn),比其他多了二分之一左右。其余的四個(gè),則沒(méi)有明顯區(qū)別。
感謝各位的閱讀!看完上述內(nèi)容,你們對(duì)C#單例模式的實(shí)現(xiàn)原理大概了解了嗎?希望文章內(nèi)容對(duì)大家有所幫助。如果想了解更多相關(guān)文章內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。