寫這個系列的文章,只爲把所學的設計模式再系統的整理一遍。錯誤和不周到的地方歡迎你們批評。點擊這裏下載源代碼。html
在程序運行時,某種類型只須要一個實例時,通常採用單例模式。爲何須要一個實例?第一,性能,第二,保持代碼簡潔,好比程序中經過某個配置類A讀取配置文件,若是在每處使用的地方都new A(),才能讀取配置項,一個是浪費系統資源(參考.NET垃圾回收機制),再者重複代碼太多。程序員
實現單例模式,方法很是多,這裏我把見過的方式都過一遍,來體會如何在支持併發訪問、性能、代碼簡潔程度等方面不斷追求極致。(單擊此處下載代碼)c#
實現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}]建立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: }
爲了可以在下面的測試代碼中展現上面代碼的問題,這裏在建立對象前,讓線程休息1秒,而且在控制檯打印出當前線程ID、對象的hashcode(通常不一樣對象的hashcode是不同的,但可能重複)。安全
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(); // 阻止主線程結束
21: }
22:
23: private static void Compute()
24: {
25: Singleton o1 = Singleton.Instance;
26: }
27: }
28: }
執行結果以下:架構
分析:併發
Singleton.Instance的get方法中建立instance並未考慮併發訪問的狀況,致使可能重複建立Singleton對象。下面的實現方法修復了此問題。app
實現2:簡單線程安全ide
要解決上面的問題,最簡單的方法就是在建立對象的時候加鎖。函數
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}]建立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(); // 阻止主線程結束
27: }
28:
29: private static void Compute()
30: {
31: Singleton o1 = Singleton.Instance;
32: }
33: }
34: }
咱們再看看執行效果:
建立Singleton只執行一次。可是這種寫法性能並不高,每次經過Singleton.Instance得到實例對象都須要判斷鎖是否別別的線程佔用。
這裏咱們修改一下Singleton,把代碼中的Thread.Sleep和Console.Writeline都去掉,這裏我從新建立了一個Singleton2 class,兩個線程中循環調用100000000次,看一下這麼實現的性能:
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(); // 阻止主線程結束
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: }
執行結果:
咱們先不討論結果,接着往下看看雙檢鎖方式的性能。
實現3:雙檢鎖實現的線程安全
Singleton雙檢鎖實現:
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: }
測試代碼和上面的同樣,結果以下:
性能提升了(7571+7465-1410-1412)/ (7571+7465) * 100% = 81.2%。(實際項目中爲了減小偏差,應該跑多遍測試獲得多個結果的平均值和方差,這裏爲了方便,我只把一次測試結果貼出來。)
雙檢鎖機制在lock外又檢查了一次instance是否爲null,這樣在第一次訪問使instance建立後,後面的調用都無需檢查lock是否被佔用。
一名程序員要了解到這裏算基本合格,若是想達到更高的水平,繼續往下看。這種方式有什麼缺點呢?
實現4:非懶加載,無鎖實現線程安全
.NET中的static變量在class被第一次實例化的時候建立,且保證僅執行一次建立。利用這個特色,能夠像以下實現:
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: }
代碼很是簡潔。可是爲何有個靜態構造函數呢,咱們看看下面的測試代碼:
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: }
執行結果以下:
把靜態構造函數去掉後執行結果以下:
這是由於沒有靜態構造函數的類,編譯時會被標記稱beforefieldinit,那麼,beforefieldinit究竟表示什麼樣的語義呢?Scott Allen對此進行了詳細的解釋:beforefieldinit爲CLR提供了在任什麼時候候執行.cctor的受權,只要該方法在第一次訪問類型的靜態字段以前執行便可。
實現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: }
實現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: }