三种方式构建C#单例模式

  1     /// <summary>
  2     /// 双检锁实现单例
  3     /// </summary>
  4     public sealed class SingletonDoubleCheck
  5     {
  6         //s_lock对象是实现线程安全所需要的,定义这个对象时,我们假设创建单例对象的代价高于创建一个System.Object对象
  7         //并假设可能根本不需要创建单例对象,否则,更经济、更简单的做法是在一个类构造器中创建单例对象
  8         private static Object s_lock = new Object();
  9 
 10         //这个字段引用一个单例对象
 11         private static SingletonDoubleCheck s_value = null;
 12 
 13         //私有构造器阻止这个类外部的任何代码创建实例
 14         private SingletonDoubleCheck()
 15         {
 16 
 17         }
 18 
 19         //以下公共静态方法返回单例对象(如有必要就创建它)
 20         public static SingletonDoubleCheck GetSingleton()
 21         {
 22             //如果单例对象已经创建,则直接返回它
 23             if (s_value != null)
 24             {
 25                 return s_value;
 26             }
 27 
 28             //在指定对象上获取排他锁
 29             Monitor.Enter(s_lock);
 30 
 31             //再次检查是否已经创建
 32             //解决问题:若多个线程首次(单例对象还未创建)同时进入,此时,只有一个线程执行下面的代码(因为有Monitor),
 33             //当该线程(第一个线程)创建完实例后,另一个线程(第二个线程)立即获得锁,也执行下面的代码,
 34             //此时实例已经创建完成,后面的线程应该直接返回才对,因此再判断一次实例是否已经创建。
 35             if (s_value == null)
 36             {
 37                 //若仍未创建则创建它
 38                 SingletonDoubleCheck singleton = new SingletonDoubleCheck();
 39 
 40                 //将singleton给s_value
 41                 //下面的代码保证singleton中的引用只有在构造器结束执行之后才发布到s_value中
 42                 Volatile.Write(ref s_value, singleton);
 43 
 44                 //注意:下面的写法是不牢靠的
 45                 //因为编译器可能会这样做:
 46                 //1.为SingletonDoubleCheck分配内存
 47                 //2.将引用发布到(赋给)s_value
 48                 //3.调用构造器
 49                 //假设在将引用发布给s_value之后,但在调用构造器之前,若有另一个线程调用了GetSingleton,
 50                 //此时s_value不为null,该线程会使用该对象,但该对象的构造器还没执行完成。
 51                 //s_value = new SingletonDoubleCheck();
 52             }
 53 
 54             //释放指定对象上的排他锁
 55             Monitor.Exit(s_lock);
 56 
 57             return s_value;
 58         }
 59     }
 60 
 61     /// <summary>
 62     /// C#下简单的构造单例方法
 63     /// CLR已保证了对类的构造是线程安全的,书写非常简便
 64     /// 缺点也很明显,首次访问类的任何成员时都会调用类型构造器
 65     /// 所以,如果该类定义了其它静态成员,就会在访问其它任何静态成员时创建该对象
 66     /// </summary>
 67     public sealed class SingletonSimple
 68     {
 69         private static SingletonSimple s_value = new SingletonSimple();
 70 
 71         //私有构造器阻止这个类外部的任何代码创建实例
 72         private SingletonSimple()
 73         {
 74 
 75         }
 76 
 77         //以下公共静态方法返回单例对象(如有必要就创建它)
 78         public static SingletonSimple GetSingleton()
 79         {
 80             return s_value;
 81         }
 82 
 83         //或
 84         public static SingletonSimple SingletonInstance
 85         {
 86             get { return s_value; }
 87         }
 88 
 89         //或
 90         public static SingletonSimple Instance { get; } = new SingletonSimple();
 91     }
 92 
 93     /// <summary>
 94     /// 嵌套类实现单例
 95     /// 如果多个线程同时调用GetSingleton,则可能创建多个SingletonNested对象
 96     /// 但由于使用了Interlocked.CompareExchange,所以保证只会有一个引用被发布到s_value
 97     /// 没有被固定下来的对象都会被垃圾回收
 98     /// 该种方式不会阻塞线程
 99     /// </summary>
100     public sealed class SingletonNested
101     {
102         private static SingletonNested s_value = null;
103 
104         //私有构造器阻止这个类外部的任何代码创建实例
105         private SingletonNested()
106         {
107 
108         }
109 
110         //以下公共静态方法返回单例对象(如有必要就创建它)
111         public static SingletonNested GetSingleton()
112         {
113             //如果单例对象已经创建,则直接返回它
114             if (s_value != null)
115             {
116                 return s_value;
117             }
118 
119             //创建一个新的单例对象,并把它固定下来(如果另一个线程还没有固定它的话)
120             SingletonNested singletonNested = new SingletonNested();
121 
122             //比较两个指定的引用类型的实例 T 是否相等,如果相等,则替换第一个,并且返回s_value原始值
123             //s_value与null比较,如果相等则用singletonNested替换s_value,否则不替换
124             Interlocked.CompareExchange(ref s_value, singletonNested, null);
125 
126             //如果该线程竞争失败,则新建的第二个单实例对象会被垃圾回收
127 
128             return s_value;
129         }
130     }