几乎所有的人都能对单例进行一般的解释,但是真正让他们手写一段单例代码的时候,能写得清楚明白的人就没几个了。下面我们一起来总结一下怎么写出一个好的单例。[源代码从这里下载]
目录
一、非线程安全的单例
二、未采用延迟加载的单例
三、采用线程同步方法实现的单例
四、采用双重锁实现的单例
五、【结论】采用原子操作实现的单例
一、非线程安全的单例:
1 public class Singleton
2 {
3 private static Singleton instance;
4 private Singleton() { }
5
6 public static Singleton GetInstance
7 {
8 get
9 {
10 if(null == instance)
11 {
12 instance = new Singleton();
13 }
14 return instance;
15 }
16 }
17 }
这种写法存在的问题是,当并发调用GetInstance属性的时候,由于多个线程同时进入了if判断,从而造成返回多个Singleton对象的隐患。这自然就有违我们的单例模式了。
二、未采用延迟加载的单例:
1 public class Singleton
2 {
3 private static Singleton instance = new Singleton();
4 private Singleton() { }
5
6 public static Singleton GetInstance
7 {
8 get { return instance; }
9 }
10 }
由于采用框架特性,这种写法确实是线程安全的,但是类型一旦加载,单例对象就已经存在,造成多余开销,如果构造函数耗时比较长的话,影响就更为突出了。
三、采用线程同步方法实现的单例:
1 public class Singleton
2 {
3 private static Singleton instance;
4 private Singleton() { }
5
6 [MethodImpl(MethodImplOptions.Synchronized)]
7 public static Singleton GetInstance()
8 {
9 if (null == instance)
10 {
11 instance = new Singleton();
12 }
13 return instance;
14 }
15 }
线程安全了,也延迟加载了,但是新的问题又来了,由于GetInstance方法在任何时候都只允许一个线程进入,带来的性能损失是不可忽视的。
四、采用双重锁实现的单例:
1 public class Singleton
2 {
3 private static readonly object syncObj = new object();
4 private static Singleton instance;
5 private Singleton() { }
6
7 public static Singleton GetInstance
8 {
9 get
10 {
11 if (null == instance)
12 {
13 lock (syncObj)
14 {
15 if (null == instance)
16 {
17 instance = new Singleton();
18 }
19 }
20 }
21 return instance;
22 }
23 }
24 }
这样实现总该完美了吧,线程安全、延迟加载、效率都没有问题。但是如此写法稍显臃肿,就仅仅为了实现一个单例,搞了这么大一串括号,还为此引入了一个syncObj对象,还有更好的实现方式吗?
五、【结论】采用原子操作实现的单例:
1 public class Singleton
2 {
3 private static Singleton instance;
4 private Singleton() { }
5
6 public static Singleton GetInstance
7 {
8 get
9 {
10 if (null == instance)
11 {
12 System.Threading.Interlocked.CompareExchange(ref instance, new Singleton(), null);
13 }
14 return instance;
15 }
16 }
17 }
这种写法可以解决以上提到的所有问题,唯一需要提醒一点的是,在调用原子操作之前的判断语句是必不可少的,因为一旦执行CompareExchange方法必然会调用Singleton类型的构造函数,虽然不会引发多个instance对象的问题,但是会造成性能损失,如果构造函数耗时过长,影响将更为严重,所以在调用原子操作之前,先进性单例的判断,就能解决以上问题。