欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程语言 > asp.net >内容正文

asp.net

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

发布时间:2025/7/14 asp.net 48 豆豆
生活随笔 收集整理的这篇文章主要介绍了 设计模式之一:单例模式(Singleton Pattern) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

写这个系列的文章,只为把所学的设计模式再系统的整理一遍。错误和不周到的地方欢迎大家批评。点击这里下载源代码。

什么时候使用单例模式

在程序运行时,某种类型只需要一个实例时,一般采用单例模式。为什么需要一个实例?第一,性能,第二,保持代码简洁,比如程序中通过某个配置类A读取配置文件,如果在每处使用的地方都new A(),才能读取配置项,一个是浪费系统资源(参考.NET垃圾回收机制),再者重复代码太多。

单例模式的实现

实现单例模式,方法非常多,这里我把见过的方式都过一遍,来体会如何在支持并发访问、性能、代码简洁程度等方面不断追求极致。(单击此处下载代码)

实现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对象。下面的实现方法修复了此问题。

实现2:简单线程安全

要解决上面的问题,最简单的方法就是在创建对象的时候加锁。

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是否被占用。

一名程序员要了解到这里算基本合格,如果想达到更高的水平,继续往下看。这种方式有什么缺点呢?

  • 上面的代码在Java中不能正常工作。这是因为Java的Memory Model实现和.NET不一样,并不保证一定在构造函数执行完成后才返回对象的引用。虽然Java 1.5版本重构了Memory Model,但是双检锁机制在不给instance field加volatile关键字时,依然不能正常工作。
  • Microsoft的.net memory model并不是按照标准的ECMA CLI规范实现,而是在标准上做了一些“增强”工作。MS .net CLR memory model中所有的写操作都是VolatileWrite(参考《CLR via C#》第二版的第24章)。所以我们的代码中不加volatile也能在IA64CPU 架构的机器上正常执行。但是如Jeffrey建议,最好还是遵循ECMA标准。
  • 实现复杂。

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

参考:

  • Exploring the Singleton Design Pattern
  • C#设计模式(7)-Singleton Pattern
  • Implementing the Singleton Pattern in C#
  • c#静态构造函数
  • C# and beforefieldinit
  • 《研磨设计模式》
  • 关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释
  • [你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器
  • 转载于:https://www.cnblogs.com/EthanCai/p/3150595.html

    总结

    以上是生活随笔为你收集整理的设计模式之一:单例模式(Singleton Pattern)的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。