设计模式之一:单例模式(Singleton Pattern)
寫(xiě)這個(gè)系列的文章,只為把所學(xué)的設(shè)計(jì)模式再系統(tǒng)的整理一遍。錯(cuò)誤和不周到的地方歡迎大家批評(píng)。點(diǎn)擊這里下載源代碼。
什么時(shí)候使用單例模式
在程序運(yùn)行時(shí),某種類(lèi)型只需要一個(gè)實(shí)例時(shí),一般采用單例模式。為什么需要一個(gè)實(shí)例?第一,性能,第二,保持代碼簡(jiǎn)潔,比如程序中通過(guò)某個(gè)配置類(lèi)A讀取配置文件,如果在每處使用的地方都new A(),才能讀取配置項(xiàng),一個(gè)是浪費(fèi)系統(tǒng)資源(參考.NET垃圾回收機(jī)制),再者重復(fù)代碼太多。
單例模式的實(shí)現(xiàn)
實(shí)現(xiàn)單例模式,方法非常多,這里我把見(jiàn)過(guò)的方式都過(guò)一遍,來(lái)體會(huì)如何在支持并發(fā)訪(fǎng)問(wèn)、性能、代碼簡(jiǎn)潔程度等方面不斷追求極致。(單擊此處下載代碼)
實(shí)現(xiàn)1:非線(xiàn)程安全
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Threading.Tasks; 7:? 8: namespace SingletonPatternNotTheadSafe 9: { 10: public sealed class Singleton 11: { 12: private static Singleton instance = null; 13:? 14: private Singleton() 15: { 16: } 17:? 18: public static Singleton Instance 19: { 20: get 21: { 22: if (instance == null) 23: { 24: Thread.Sleep(1000); 25: instance = new Singleton(); 26: Console.WriteLine(string.Format( 27: "[{0}]創(chuàng)建Singleton {1}" , Thread.CurrentThread.ManagedThreadId, instance.GetHashCode())); 28: } 29:? 30: Console.WriteLine(string.Format( 31: "[{0}]獲得Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode())); 32: return instance; 33: } 34: } 35: } 36: }為了能夠在下面的測(cè)試代碼中展示上面代碼的問(wèn)題,這里在創(chuàng)建對(duì)象前,讓線(xiàn)程休息1秒,并且在控制臺(tái)打印出當(dāng)前線(xiàn)程ID、對(duì)象的hashcode(一般不同對(duì)象的hashcode是不一樣的,但可能重復(fù))。
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Threading.Tasks; 7:? 8: namespace SingletonPatternNotTheadSafe 9: { 10: class Program 11: { 12: private static void Main(string[] args) 13: { 14: Thread t1 = new Thread(new ThreadStart(Compute)); 15:? 16: t1.Start(); 17:? 18: Compute(); 19:? 20: Console.ReadLine(); // 阻止主線(xiàn)程結(jié)束 21: } 22:? 23: private static void Compute() 24: { 25: Singleton o1 = Singleton.Instance; 26: } 27: } 28: }執(zhí)行結(jié)果如下:
分析:
Singleton.Instance的get方法中創(chuàng)建instance并未考慮并發(fā)訪(fǎng)問(wèn)的情況,導(dǎo)致可能重復(fù)創(chuàng)建Singleton對(duì)象。下面的實(shí)現(xiàn)方法修復(fù)了此問(wèn)題。
實(shí)現(xiàn)2:簡(jiǎn)單線(xiàn)程安全
要解決上面的問(wèn)題,最簡(jiǎn)單的方法就是在創(chuàng)建對(duì)象的時(shí)候加鎖。
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Threading.Tasks; 7:? 8: namespace SingletonSimpleThreadSafe 9: { 10: public sealed class Singleton 11: { 12: private static Singleton instance = null; 13: private static readonly object _lock = new object(); 14:? 15: private Singleton() 16: { 17: } 18:? 19: public static Singleton Instance 20: { 21: get 22: { 23: lock (_lock) 24: { 25: if (instance == null) 26: { 27: Thread.Sleep(1000); 28: instance = new Singleton(); 29: Console.WriteLine(string.Format( 30: "[{0}]創(chuàng)建Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode())); 31: } 32: } 33:? 34: Console.WriteLine(string.Format( 35: "[{0}]獲得Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode())); 36: return instance; 37: } 38: } 39: } 40: }測(cè)試代碼如下:
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Diagnostics; 7: using System.Threading.Tasks; 8:? 9: namespace SingletonSimpleThreadSafe 10: { 11: class Program 12: { 13: private static void Main(string[] args) 14: { 15: SingletonTest(); 16: } 17:? 18: private static void SingletonTest() 19: { 20: Thread t1 = new Thread(new ThreadStart(Compute)); 21:? 22: t1.Start(); 23:? 24: Compute(); 25:? 26: Console.ReadLine(); // 阻止主線(xiàn)程結(jié)束 27: } 28:? 29: private static void Compute() 30: { 31: Singleton o1 = Singleton.Instance; 32: } 33: } 34: }我們?cè)倏纯磮?zhí)行效果:
創(chuàng)建Singleton只執(zhí)行一次。但是這種寫(xiě)法性能并不高,每次通過(guò)Singleton.Instance獲得實(shí)例對(duì)象都需要判斷鎖是否別別的線(xiàn)程占用。
這里我們修改一下Singleton,把代碼中的Thread.Sleep和Console.Writeline都去掉,這里我重新創(chuàng)建了一個(gè)Singleton2 class,兩個(gè)線(xiàn)程中循環(huán)調(diào)用100000000次,看一下這么實(shí)現(xiàn)的性能:
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Threading.Tasks; 7:? 8: namespace SingletonSimpleThreadSafe 9: { 10: public sealed class Singleton2 11: { 12: private static Singleton2 instance = null; 13: private static readonly object _lock = new object(); 14:? 15: private Singleton2() 16: { 17: } 18:? 19: public static Singleton2 Instance 20: { 21: get 22: { 23: lock (_lock) 24: { 25: if (instance == null) 26: { 27: instance = new Singleton2(); 28: } 29: } 30:? 31: return instance; 32: } 33: } 34: } 35: }測(cè)試代碼如下:
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Diagnostics; 7: using System.Threading.Tasks; 8:? 9: namespace SingletonSimpleThreadSafe 10: { 11: class Program 12: { 13: private static void Main(string[] args) 14: { 15: Singleton2Test(); 16: } 17:? 18: private static void Singleton2Test() 19: { 20: Thread t1 = new Thread(new ThreadStart(Compute2)); 21:? 22: t1.Start(); 23:? 24: Compute2(); 25:? 26: Console.ReadLine(); // 阻止主線(xiàn)程結(jié)束 27: } 28:? 29: private static void Compute2() 30: { 31: Stopwatch sw1 = new Stopwatch(); 32:? 33: sw1.Start(); 34:? 35: for (int i = 0; i < 100000000; i++) 36: { 37: Singleton2 instance = Singleton2.Instance; 38: } 39:? 40: sw1.Stop(); 41:? 42: Console.WriteLine(string.Format("[{0}]耗時(shí):{1}毫秒", 43: Thread.CurrentThread.ManagedThreadId, 44: sw1.ElapsedMilliseconds)); 45: } 46: } 47: }執(zhí)行結(jié)果:
我們先不討論結(jié)果,接著往下看看雙檢鎖方式的性能。
實(shí)現(xiàn)3:雙檢鎖實(shí)現(xiàn)的線(xiàn)程安全
Singleton雙檢鎖實(shí)現(xiàn):
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading; 6: using System.Threading.Tasks; 7:? 8: namespace SingletonDoubleCheckThreadSafe 9: { 10: public sealed class Singleton2 11: { 12: private static Singleton2 instance = null; 13: private static readonly object _lock = new object(); 14:? 15: private Singleton2() 16: { 17: } 18:? 19: public static Singleton2 Instance 20: { 21: get 22: { 23: if (instance == null) 24: { 25: lock (_lock) 26: { 27: if (instance == null) 28: { 29: instance = new Singleton2(); 30: } 31: } 32: } 33:? 34: return instance; 35: } 36: } 37: } 38: }測(cè)試代碼和上面的一樣,結(jié)果如下:
性能提高了(7571+7465-1410-1412)/ (7571+7465) * 100% = 81.2%。(實(shí)際項(xiàng)目中為了減少誤差,應(yīng)該跑多遍測(cè)試得到多個(gè)結(jié)果的平均值和方差,這里為了方便,我只把一次測(cè)試結(jié)果貼出來(lái)。)
雙檢鎖機(jī)制在lock外又檢查了一次instance是否為null,這樣在第一次訪(fǎng)問(wèn)使instance創(chuàng)建后,后面的調(diào)用都無(wú)需檢查lock是否被占用。
一名程序員要了解到這里算基本合格,如果想達(dá)到更高的水平,繼續(xù)往下看。這種方式有什么缺點(diǎn)呢?
- 上面的代碼在Java中不能正常工作。這是因?yàn)镴ava的Memory Model實(shí)現(xiàn)和.NET不一樣,并不保證一定在構(gòu)造函數(shù)執(zhí)行完成后才返回對(duì)象的引用。雖然Java 1.5版本重構(gòu)了Memory Model,但是雙檢鎖機(jī)制在不給instance field加volatile關(guān)鍵字時(shí),依然不能正常工作。
- Microsoft的.net memory model并不是按照標(biāo)準(zhǔn)的ECMA CLI規(guī)范實(shí)現(xiàn),而是在標(biāo)準(zhǔn)上做了一些“增強(qiáng)”工作。MS .net CLR memory model中所有的寫(xiě)操作都是VolatileWrite(參考《CLR via C#》第二版的第24章)。所以我們的代碼中不加volatile也能在IA64CPU 架構(gòu)的機(jī)器上正常執(zhí)行。但是如Jeffrey建議,最好還是遵循ECMA標(biāo)準(zhǔn)。
- 實(shí)現(xiàn)復(fù)雜。
實(shí)現(xiàn)4:非懶加載,無(wú)鎖實(shí)現(xiàn)線(xiàn)程安全
.NET中的static變量在class被第一次實(shí)例化的時(shí)候創(chuàng)建,且保證僅執(zhí)行一次創(chuàng)建。利用這個(gè)特點(diǎn),可以像如下實(shí)現(xiàn):
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading.Tasks; 6:? 7: namespace SingletonNotUsingLock 8: { 9: public class Singleton 10: { 11: private volatile static Singleton instance = new Singleton(); 12:? 13: // Explicit static constructor to tell C# compiler 14: // not to mark type as beforefieldinit 15: static Singleton() 16: { 17: Console.WriteLine("execute static constructor"); 18: } 19:? 20: private Singleton() 21: { 22: Console.WriteLine("execute private constructor"); 23: } 24:? 25: public static Singleton Instance 26: { 27: get 28: { 29: Console.WriteLine("instance get"); 30: return instance; 31: } 32: } 33: } 34: }上面的代碼可以更簡(jiǎn)化一些,去掉Instance屬性,將私有的instance變量改成public的:
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading.Tasks; 6:? 7: namespace SingletonNotUsingLock 8: { 9: public class Singleton2 10: { 11: public volatile static Singleton2 instance = new Singleton2(); 12:? 13: // Explicit static constructor to tell C# compiler 14: // not to mark type as beforefieldinit 15: static Singleton2() 16: { 17: Console.WriteLine("execute static constructor"); 18: } 19:? 20: private Singleton2() 21: { 22: Console.WriteLine("execute private constructor"); 23: } 24: } 25: }代碼非常簡(jiǎn)潔。但是為什么有個(gè)靜態(tài)構(gòu)造函數(shù)呢,我們看看下面的測(cè)試代碼:
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading.Tasks; 6:? 7: namespace SingletonNotUsingLock 8: { 9: class Program 10: { 11: static void Main(string[] args) 12: { 13: Console.WriteLine("begin create singleton"); 14:? 15: Singleton s1 = Singleton.Instance; 16:? 17: Console.WriteLine("after create singleton"); 18:? 19: Singleton2 s2 = Singleton2.instance; 20:? 21: Console.WriteLine("after create singleton2"); 22: } 23: } 24: }執(zhí)行結(jié)果如下:
把靜態(tài)構(gòu)造函數(shù)去掉后執(zhí)行結(jié)果如下:
這是因?yàn)闆](méi)有靜態(tài)構(gòu)造函數(shù)的類(lèi),編譯時(shí)會(huì)被標(biāo)記稱(chēng)beforefieldinit,那么,beforefieldinit究竟表示什么樣的語(yǔ)義呢?Scott Allen對(duì)此進(jìn)行了詳細(xì)的解釋:beforefieldinit為CLR提供了在任何時(shí)候執(zhí)行.cctor的授權(quán),只要該方法在第一次訪(fǎng)問(wèn)類(lèi)型的靜態(tài)字段之前執(zhí)行即可。
實(shí)現(xiàn)5:無(wú)鎖懶加載
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading.Tasks; 6:? 7: namespace SingletonNotUsingLockAndLazyLoad 8: { 9: public class Singleton 10: { 11: private Singleton() 12: { 13: Console.WriteLine("execute Singleton private constructor"); 14: } 15:? 16: public static Singleton Instance 17: { 18: 19: get 20: { 21: Console.WriteLine("execute Singleton.Instance get"); 22: return Nested.instance; 23: } 24: } 25:? 26: private class Nested 27: { 28: // Explicit static constructor to tell C# compiler 29: // not to mark type as beforefieldinit 30: static Nested() 31: { 32: Console.WriteLine("execute Nested static constructor"); 33: } 34:? 35: internal static readonly Singleton instance = new Singleton(); 36: } 37: } 38: }實(shí)現(xiàn)6:使用.NET 4.0中的Lazy<T>
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Threading.Tasks; 6:? 7: namespace SingletonUsingLazyType 8: { 9: public sealed class Singleton 10: { 11: private static readonly Lazy<Singleton> lazy = 12: new Lazy<Singleton>(() => new Singleton()); 13:? 14: public static Singleton Instance { get { return lazy.Value; } } 15:? 16: private Singleton() 17: { 18: } 19: } 20: }參考:
轉(zhuǎn)載于:https://www.cnblogs.com/EthanCai/p/3150595.html
總結(jié)
以上是生活随笔為你收集整理的设计模式之一:单例模式(Singleton Pattern)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Windows打印机驱动开发笔记(一)
- 下一篇: 好久没写了,重装了系统重新配置的Live