三种方式构建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 }