C#單例模式的實現(xiàn)原理?這個問題可能是我們?nèi)粘W(xué)習(xí)或工作經(jīng)常見到的。希望通過這個問題能讓你收獲頗深。下面是小編給大家?guī)淼膮⒖純?nèi)容,讓我們一起來看看吧!
成都創(chuàng)新互聯(lián)服務(wù)項目包括米易網(wǎng)站建設(shè)、米易網(wǎng)站制作、米易網(wǎng)頁制作以及米易網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,米易網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到米易省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!簡介
單例指的是只能存在一個實例的類(在C#中,更準確的說法是在每個AppDomain之中只能存在一個實例的類,它是軟件工程中使用最多的幾種模式之一。在第一個使用者創(chuàng)建了這個類的實例之后,其后需要使用這個類的就只能使用之前創(chuàng)建的實例,無法再創(chuàng)建一個新的實例。通常情況下,單例會在第一次被使用時創(chuàng)建。本文會對C#中幾種單例的實現(xiàn)方式進行介紹,并分析它們之間的線程安全性和性能差異。
單例的實現(xiàn)方式有很多種,但從最簡單的實現(xiàn)(非延遲加載,非線程安全,效率低下),到可延遲加載,線程安全,且高效的實現(xiàn),它們都有一些基本的共同點:
單例類都只有一個private的無參構(gòu)造函數(shù)
類聲明為sealed(不是必須的)
類中有一個靜態(tài)變量保存著所創(chuàng)建的實例的引用
單例類會提供一個靜態(tài)方法或?qū)傩詠矸祷貏?chuàng)建的實例的引用(eg.GetInstance)
幾種實現(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; } } }
這種方法不是線程安全的,會存在兩個線程同時執(zhí)行if (instance == null)并且創(chuàng)建兩個不同的instance,后創(chuàng)建的會替換掉新創(chuàng)建的,導(dǎo)致之前拿到的reference為空。
二簡單的線程安全實現(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; } } } }
相比較于實現(xiàn)一,這個版本加上了一個對instance的鎖,在調(diào)用instance之前要先對padlock上鎖,這樣就避免了實現(xiàn)一中的線程沖突,該實現(xiàn)自始至終只會創(chuàng)建一個instance了。但是,由于每次調(diào)用Instance都會使用到鎖,而調(diào)用鎖的開銷較大,這個實現(xiàn)會有一定的性能損失。
注意這里我們使用的是新建一個private的object實例padlock來實現(xiàn)鎖操作,而不是直接對Singleton進行上鎖。直接對類型上鎖會出現(xiàn)潛在的風(fēng)險,因為這個類型是public的,所以理論上它會在任何code里調(diào)用,直接對它上鎖會導(dǎo)致性能問題,甚至?xí)霈F(xiàn)死鎖情況。
Note: C#中,同一個線程是可以對一個object進行多次上鎖的,但是不同線程之間如果同時上鎖,就可能會出現(xiàn)線程等待,或者嚴重的會出現(xiàn)死鎖情況。因此,我們在使用lock時,盡量選擇類中的私有變量上鎖,這樣可以避免上述情況發(fā)生。
三雙重驗證的線程安全實現(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; } } }
在保證線程安全的同時,這個實現(xiàn)還避免了每次調(diào)用Instance都進行l(wèi)ock操作,這會節(jié)約一定的時間。
但是,這種實現(xiàn)也有它的缺點:
1無法在Java中工作。(具體原因可以見原文,這邊沒怎么理解)
2程序員在自己實現(xiàn)時很容易出錯。如果對這個模式的代碼進行自己的修改,要倍加小心,因為double check的邏輯較為復(fù)雜,很容易出現(xiàn)思考不周而出錯的情況。
四不用鎖的線程安全實現(xiàn)
public sealed class Singleton { //在Singleton第一次被調(diào)用時會執(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; } } }
這個實現(xiàn)很簡單,并沒有用到鎖,但是它仍然是線程安全的。這里使用了一個static,readonly的Singleton實例,它會在Singleton第一次被調(diào)用的時候新建一個instance,這里新建時候的線程安全保障是由.NET直接控制的,我們可以認為它是一個原子操作,并且在一個AppDomaing中它只會被創(chuàng)建一次。
這種實現(xiàn)也有一些缺點:
1instance被創(chuàng)建的時機不明,任何對Singleton的調(diào)用都會提前創(chuàng)建instance
2static構(gòu)造函數(shù)的循環(huán)調(diào)用。如有A,B兩個類,A的靜態(tài)構(gòu)造函數(shù)中調(diào)用了B,而B的靜態(tài)構(gòu)造函數(shù)中又調(diào)用了A,這兩個就會形成一個循環(huán)調(diào)用,嚴重的會導(dǎo)致程序崩潰。
3我們需要手動添加Singleton的靜態(tài)構(gòu)造函數(shù)來確保Singleton類型不會被自動加上beforefieldinit這個Attribute,以此來確保instance會在第一次調(diào)用Singleton時才被創(chuàng)建。
4readonly的屬性無法在運行時改變,如果我們需要在程序運行時dispose這個instance再重新創(chuàng)建一個新的instance,這種實現(xiàn)方法就無法滿足。
五完全延遲加載實現(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(); } }
實現(xiàn)五是實現(xiàn)四的包裝。它確保了instance只會在Instance的get方法里面調(diào)用,且只會在第一次調(diào)用前初始化。它是實現(xiàn)四的確保延遲加載的版本。
六 使用.NET4的Lazy
public sealed class Singleton { private static readonly Lazylazy = new Lazy (() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() { } }
.NET4或以上的版本支持Lazy
性能差異
之前的實現(xiàn)中,我們都在強調(diào)代碼的線程安全性和延遲加載。然而在實際使用中,如果你的單例類的初始化不是一個很耗時的操作或者初始化順序不會導(dǎo)致bug,延遲初始化是一個可有可無的特性,因為初始化所占用的時間是可以忽略不計的。
在實際使用場景中,如果你的單例實例會被頻繁得調(diào)用(如在一個循環(huán)中),那么為了保證線程安全而帶來的性能消耗是更值得關(guān)注的地方。
為了比較這幾種實現(xiàn)的性能,我做了一個小測試,循環(huán)拿這些實現(xiàn)中的單例9億次,每次調(diào)用instance的方法執(zhí)行一個count++操作,每隔一百萬輸出一次,運行環(huán)境是MBP上的Visual Studio for Mac。結(jié)果如下:
線程安全性 | 延遲加載 | 測試運行時間(ms) | |
---|---|---|---|
實現(xiàn)一 | 否 | 是 | 15532 |
實現(xiàn)二 | 是 | 是 | 45803 |
實現(xiàn)三 | 是 | 是 | 15953 |
實現(xiàn)四 | 是 | 不完全 | 14572 |
實現(xiàn)五 | 是 | 是 | 14295 |
實現(xiàn)六 | 是 | 是 | 22875 |
測試方法并不嚴謹,但是仍然可以看出,方法二由于每次都需要調(diào)用lock,是最耗時的,幾乎是其他幾個的三倍。排第二的則是使用.NET Lazy類型的實現(xiàn),比其他多了二分之一左右。其余的四個,則沒有明顯區(qū)別。
感謝各位的閱讀!看完上述內(nèi)容,你們對C#單例模式的實現(xiàn)原理大概了解了嗎?希望文章內(nèi)容對大家有所幫助。如果想了解更多相關(guān)文章內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)成都網(wǎng)站設(shè)計公司行業(yè)資訊頻道。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。