日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

设计模式之一:单例模式(Singleton Pattern)

發(fā)布時間:2025/7/14 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 设计模式之一:单例模式(Singleton Pattern) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

寫這個系列的文章,只為把所學(xué)的設(shè)計模式再系統(tǒng)的整理一遍。錯誤和不周到的地方歡迎大家批評。點擊這里下載源代碼。

什么時候使用單例模式

在程序運行時,某種類型只需要一個實例時,一般采用單例模式。為什么需要一個實例?第一,性能,第二,保持代碼簡潔,比如程序中通過某個配置類A讀取配置文件,如果在每處使用的地方都new A(),才能讀取配置項,一個是浪費系統(tǒng)資源(參考.NET垃圾回收機制),再者重復(fù)代碼太多。

單例模式的實現(xiàn)

實現(xiàn)單例模式,方法非常多,這里我把見過的方式都過一遍,來體會如何在支持并發(fā)訪問、性能、代碼簡潔程度等方面不斷追求極致。(單擊此處下載代碼)

實現(xiàn)1:非線程安全

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: }

為了能夠在下面的測試代碼中展示上面代碼的問題,這里在創(chuàng)建對象前,讓線程休息1秒,并且在控制臺打印出當前線程ID、對象的hashcode(一般不同對象的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(); // 阻止主線程結(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ā)訪問的情況,導(dǎo)致可能重復(fù)創(chuàng)建Singleton對象。下面的實現(xiàn)方法修復(fù)了此問題。

實現(xiàn)2:簡單線程安全

要解決上面的問題,最簡單的方法就是在創(chuàng)建對象的時候加鎖。

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: }

測試代碼如下:

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(); // 阻止主線程結(jié)束 27: } 28:? 29: private static void Compute() 30: { 31: Singleton o1 = Singleton.Instance; 32: } 33: } 34: }

我們再看看執(zhí)行效果:

創(chuàng)建Singleton只執(zhí)行一次。但是這種寫法性能并不高,每次通過Singleton.Instance獲得實例對象都需要判斷鎖是否別別的線程占用。

這里我們修改一下Singleton,把代碼中的Thread.Sleep和Console.Writeline都去掉,這里我重新創(chuàng)建了一個Singleton2 class,兩個線程中循環(huán)調(diào)用100000000次,看一下這么實現(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: }

測試代碼如下:

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(); // 阻止主線程結(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}]耗時:{1}毫秒", 43: Thread.CurrentThread.ManagedThreadId, 44: sw1.ElapsedMilliseconds)); 45: } 46: } 47: }

執(zhí)行結(jié)果:

我們先不討論結(jié)果,接著往下看看雙檢鎖方式的性能。

實現(xiàn)3:雙檢鎖實現(xiàn)的線程安全

Singleton雙檢鎖實現(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: }

測試代碼和上面的一樣,結(jié)果如下:

性能提高了(7571+7465-1410-1412)/ (7571+7465) * 100% = 81.2%。(實際項目中為了減少誤差,應(yīng)該跑多遍測試得到多個結(jié)果的平均值和方差,這里為了方便,我只把一次測試結(jié)果貼出來。

雙檢鎖機制在lock外又檢查了一次instance是否為null,這樣在第一次訪問使instance創(chuàng)建后,后面的調(diào)用都無需檢查lock是否被占用。

一名程序員要了解到這里算基本合格,如果想達到更高的水平,繼續(xù)往下看。這種方式有什么缺點呢?

  • 上面的代碼在Java中不能正常工作。這是因為Java的Memory Model實現(xiàn)和.NET不一樣,并不保證一定在構(gòu)造函數(shù)執(zhí)行完成后才返回對象的引用。雖然Java 1.5版本重構(gòu)了Memory Model,但是雙檢鎖機制在不給instance field加volatile關(guān)鍵字時,依然不能正常工作。
  • Microsoft的.net memory model并不是按照標準的ECMA CLI規(guī)范實現(xiàn),而是在標準上做了一些“增強”工作。MS .net CLR memory model中所有的寫操作都是VolatileWrite(參考《CLR via C#》第二版的第24章)。所以我們的代碼中不加volatile也能在IA64CPU 架構(gòu)的機器上正常執(zhí)行。但是如Jeffrey建議,最好還是遵循ECMA標準。
  • 實現(xiàn)復(fù)雜。

實現(xiàn)4:非懶加載,無鎖實現(xiàn)線程安全

.NET中的static變量在class被第一次實例化的時候創(chuàng)建,且保證僅執(zhí)行一次創(chuàng)建。利用這個特點,可以像如下實現(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: }

上面的代碼可以更簡化一些,去掉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: }

代碼非常簡潔。但是為什么有個靜態(tài)構(gòu)造函數(shù)呢,我們看看下面的測試代碼:

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é)果如下:

這是因為沒有靜態(tài)構(gòu)造函數(shù)的類,編譯時會被標記稱beforefieldinit,那么,beforefieldinit究竟表示什么樣的語義呢?Scott Allen對此進行了詳細的解釋:beforefieldinit為CLR提供了在任何時候執(zhí)行.cctor的授權(quán),只要該方法在第一次訪問類型的靜態(tài)字段之前執(zhí)行即可。

實現(xiàn)5:無鎖懶加載

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: }

實現(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: }

參考:

  • Exploring the Singleton Design Pattern
  • C#設(shè)計模式(7)-Singleton Pattern
  • Implementing the Singleton Pattern in C#
  • c#靜態(tài)構(gòu)造函數(shù)
  • C# and beforefieldinit
  • 《研磨設(shè)計模式》
  • 關(guān)于Type Initializer和 BeforeFieldInit的問題,看看大家能否給出正確的解釋
  • [你必須知道的.NET]第二十三回:品味細節(jié),深入.NET的類型構(gòu)造器
  • 轉(zhuǎn)載于:https://www.cnblogs.com/EthanCai/p/3150595.html

    總結(jié)

    以上是生活随笔為你收集整理的设计模式之一:单例模式(Singleton Pattern)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。