单例模式是几个建立型模式中最独立的一个,它的主要目标不是根据客户程序调用生成一个新的实例,而是控制某个类型的实例数量只有一个。
GOF对单例的描述为:
Ensure a class only has one instance, and provide aglobal point of access to.
—Design Patterns : Elements of Reusable Object-Oriented Software设计模式
单例模式的应用场景没必要赘述,先来一个最简单的实现方式:安全
public class Singleton { private Singleton() { } private static Singleton instance; public static Singleton Instance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这里采用的是Lazy方式,也能够在静态变量被建立的时候直接初始化实例。
这段代码已经能够知足最初Singleton模式的设计要求,在大多数状况下能够很好地工做。但在多线程环境下这种实现方式是存在缺陷的,当多个线程几乎同时调用Singleton类的Instance静态属性的时候,instance成员可能尚未被实例化,所以它被建立了屡次,并且最终Singleton类中保存的是最后建立的那个实例,各个线程引用的对象不一样。多线程
为了保证多线程环境下instance实例只有一个,对代码进行了优化:并发
public class Singleton { private static volatile Singleton instance; public static Singleton Instance() { if (instance == null) { lock (typeof(Singleton)) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
相比最初的实现,改变的地方有这几处:ide
有些状况会破坏Singleton的封装,跳过“只能有一个实例”的限制,在实际应用中要注意规避。函数
第一种状况就是实现ICloneable接口或继承自其相关的子类,这样客户程序借助ICloneable接口就能够跳过已经被隐藏起来的构造函数高并发
另外经过二进制、Json之类序列化、反序列化的方式也能够产生新的对象。优化
前面讨论的是线程安全的Singleton实现,但有时须要的是更细粒度的Singleton,好比线程级的Singleton,只要保证在一个线程内只有一个实例便可,这就相似Asp.NET Core 自带的IOC提供的AddScope注册方式,能够保证一个HttpContext内只有一个实例。线程
虽然Asp.NET Core提供相似的现成实现,但若是在非Web环境下也须要线程级的实例控制该怎么办呢? 结合C#提供的System.ThreadStaticAttribute能够完成设计
经过System.ThreadStaticAttribute能够将某个静态变量限定为仅在本线程内部是静态的。
实现以下:
public class ThreadSingleton { private ThreadSingleton() { } [ThreadStatic] //instance只在当前线程内为静态 private static ThreadSingleton instance; public static ThreadSingleton Instance() { if (instance == null) { instance = new ThreadSingleton(); } return instance; } }
这里再不须要线程锁了,由于线程级的单例不须要考虑线程安全。
为了验证明现的准确性,首先构造一个线程内执行的目标对象:
class Work { public static IList<int> Log = new List<int>(); /// <summary> /// 每一个线程的执行部分 /// </summary> public void Procedure() { ThreadSingleton s1 = ThreadSingleton.Instance(); ThreadSingleton s2 = ThreadSingleton.Instance(); //证实能够正常构造实例 Assert.IsNotNull(s1); Assert.IsNotNull(s2); //验证当前线程执行体内两次获取的是同一个实例 Assert.AreEqual(s1.GetHashCode(), s2.GetHashCode()); //记录当前线程所使用对象的HashCode Log.Add(s1.GetHashCode()); } }
这个类会在每一个线程内部执行,并验证线程内屡次获取的Instance是同一个实例,并记录这个实例的HashCode,以便与别的线程实例对比。
接下来开启多个线程同时执行Procedure()方法:
[Test] public void ThreadSingletonTest() { int threadCount = 4; Thread[] threads = new Thread[threadCount]; //建立4个线程 for (int i = 0; i < threadCount; i++) { ThreadStart work = new ThreadStart(new Work().Procedure); threads[i] = new Thread(work); } //执行线程 foreach (var thread in threads) { thread.Start(); } Thread.Sleep(10000); Assert.AreEqual(threadCount, Work.Log.Distinct().Count()); }
Work类的静态变量Log中记录了每一个线程中实例的HashCode,这些HashCode彼此不相同,且与线程的数量一致,证实每一个线程间的实例是不相同的。
参考书籍: 王翔著 《设计模式——基于C#的工程化实现及扩展》