Singleton,单例模式,是指只产生一个实例来访问。注意看注释,下面方法是AppDomain内的单例,当然,大多数程序都是在一个AppDomain中运行。
比如下面代码就是简单的单例:
/// <summary> /// Singleton instance per AppDomain, not thread safe /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class , new () { private static T _instance; public static T Instance { get { if (_instance == default (T)) _instance = new T(); return _instance; } } } 但是它不是线程安全的,2个线程过来,很有可能创建2个实例,于是如果我们保证线程安全,还得加lock:
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class , new () { private static T _instance; private static readonly object _padLock = new object (); public static T Instance { get { lock (_padLock) { // only one thread can enter this scrope each time if (_instance == default (T)) _instance = new T(); return _instance; } } } } 虽然它已经线程安全,但是由于lock块,及时_instance != null,每次也要lock,多线程场景下性能差,所以可以采用Double Check:
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class , new () { private static T _instance; private static readonly object _padLock = new object (); public static T Instance { get { // if _instance!= default(T), it does not lock, just return if (_instance == default (T)) { lock (_padLock) { if (_instance == default (T)) _instance = new T(); } } return _instance; } } } Double check在绝大多数情况下工作的很好,但是在多核心情况下会出现创建多个实例的情况。
比如双核,CPU0执行到释放锁得时候,由于CPU高速缓存(cache)和写缓存(Store Buffer)的存在,主内存中可能_instance对象还没有创建,当CPU1执行到_instance==null时候,判断为true,然后接下去就创建了另外一个对象,因此,double check给出了2个完全不同的对象。
根据 这篇文章来看,在多处理器环境下,可以这样解决问题:
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class , new () { // add volatile to make sure _instance exist in memory, not cpu cache private volatile static T _instance; private static readonly object _padLock = new object (); public static T Instance { get { // if _instance!= default(T), it does not lock, just return if (_instance == default (T)) { lock (_padLock) { if (_instance == default (T)) _instance = new T(); } } return _instance; } } } volitile关键字将让编译器认为是多线程代码,从而不会执行优化;同时保持内存中字段总是最新的,CPU缓存将不起作用。
但是,加入volatile没有考虑到写缓存同步到主内存中,这样CPU0 new T()后,写入CPU0的cache中,而此时CPU1访问主存中的_instance,发现为null,于是它也new T(),这仅在IA64模式下可能发生,因为IA64目前是弱内存模型。而且加入volatile会带来性能开销(不用cpu cache),每次访问都访问内存,而且并发专家一再告诫我们:
,
因此,我们可以使用Thread.MemoryBarrier()内存栅栏来保证CPU不进行乱序处理,解决Cache一致性。
去掉了volatile关键字,一旦_instance初始化好,之后访问都可以利用Cpu cache。
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class , new () { private static T _instance; private static readonly object _padLock = new object (); public static T Instance { get { // if _instance!= default(T), it does not lock, just return if (_instance == default (T)) { lock (_padLock) { if (_instance == default (T)) { var temp = new T(); // The processor executing the current thread cannot reorder instructions in such a way that // memory accesses prior to the call to MemoryBarrier execute after memory accesses that // follow the call to MemoryBarrier. Thread.MemoryBarrier(); _instance = temp; } } } return _instance; } } } 如果我们要进程内单例,可以使用Mutex来保证,同样如果是多进程内单例,可以使用命名Mutex:
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class , new () { private static T _instance; private static readonly Mutex _mutex = new Mutex(); public static T Instance { get { // if _instance != default(T), it does not lock, just return if (_instance == default (T)) { _mutex.WaitOne(); if (_instance == default (T)) { var temp = new T(); // The processor executing the current thread cannot reorder instructions in such a way that // memory accesses prior to the call to MemoryBarrier execute after memory accesses that // follow the call to MemoryBarrier. Thread.MemoryBarrier(); _instance = temp; } _mutex.ReleaseMutex(); } return _instance; } } } .net本身对静态初始化是单线程访问的,因此我们可以利用这个特性,写成如下代码:
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class, new () { /// <summary> /// Lazy load singleton instance /// </summary> public static T Instance { get { return Nested.instance; } } private class Nested { /// <summary> /// .net make sure that during static constructor, it's thread safe per appdomain. /// </summary> internal static readonly T instance = new T(); } } 当然,泛型约束为new()的话,意味着T必须有public constructor,如果没有呢?比如是private constructor怎么办?
其实一般来讲,人家用private constructor,就是为了不让你new 出来一个实例,如果你非得要搞出来,可以采用以下方法:
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class { /// <summary> /// Lazy load singleton instance /// </summary> public static T Instance { get { return Nested.instance; } } private class Nested { /// <summary> /// .net make sure that during static constructor, it's thread safe per appdomain. /// </summary> internal static readonly T instance = (T)Activator.CreateInstance( typeof (T), true ); } } 自从有了.net4.0的Lazy<T>后,还可以这样搞:
/// <summary> /// Singleton instance per AppDomain /// </summary> /// <typeparam name="T"></typeparam> public static class Singleton < T > where T : class, new () { /// <summary> /// static readonly make sure _lazy is thead safe and singleton. /// using Lazy <T> make sure that it's lazy value creation is thead safe. /// </summary> private static readonly Lazy < T > _lazy = new Lazy < T > (() => new T(), true ); public static T Instance { get { return _lazy.Value; } } } 如此这般,就得到了线程安全的Singleton实例。当然我们还可以继续优化,比如有参数的构造函数,加入WeakReference等等……