概念
单例(Singleton)模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。它有三个要点:
- 该类只能有一个实例
- 必须自行创建这个实例
- 须向整个系统提供该实例
对于以上三个要点的理解:
- 该类只能有唯一的一个实例,该类无法通过其他方式创建除了其本身提供的实例之外的新实例,换言之,一旦该类提供的实例被创建,该实例则是唯一的,无法重复创建或者修改;
- 该类必须自行提供该实例,即实例化该类本身;
- 向整个系统提供该实例,很明显该单例对象须要该单例类能够为其提供一个全局访问入口
C#实现
该模式的后两点是非常容易理解的,而在实现该模式中的各种方法中对于单线程工作模式下的实现也非常简单。代码如下:
1 public sealed class Singleton 2 { 3 private static Singleton instance; 4 private Singleton() { } 5 6 public static Singleton Instance 7 { 8 get 9 { 10 if (instance == null) 11 { 12 instance = new Singleton(); 13 } 14 return instance; 15 } 16 } 17 }
但是在多线程环境中,上述代码就不能保证创建实例的唯一性了。比如当两个线程同时访问我们的Instance属性时,线程1此时已经执行了对单例对象的创建,但是还并未对我们的单例对象赋值时,线程2开始访问我们的Instance属性,由于此时的instance还未被赋值,那么线程2此时得到 的instance为null,此时线程2认为此时的单例对象还未被创建,那么就会再次创建一个单例对象,那么这个对象就不再是单例了。既然如此那么我们为什么不在第一次访问Instance时就将其加锁,后来的线程必须等待,直到线程1释放该锁,这样就可以解决线程同步的问题了。
1 public sealed class Singleton 2 { 3 private static volatile Singleton instance; 4 private static readonly object lockHelper = new object(); 5 private Singleton() { } 6 7 public static Singleton Instance 8 { 9 get 10 { 11 lock (lockHelper) 12 { 13 if (instance == null) 14 { 15 instance = new Singleton(); 16 } 17 } 18 return instance; 19 } 20 } 21 }
该方法已经能够保证创建出的实例的唯一性,但是对于访问非常频繁的单例对象,每次都对其加锁,系统开销会相应增加,但是如果我们在加锁之前对其做一次非空判断,这就会大大降低加锁的几率,提高程序的并发度,这就是著名的Double-Check Locking:
1 public sealed class Singleton 2 { 3 private static volatile Singleton instance; 4 private static readonly object lockHelper = new object(); 5 private Singleton() { } 6 7 public static Singleton Instance 8 { 9 get 10 { 11 /* 12 * 第一次检查 13 */ 14 if (instance == null) 15 { 16 lock (lockHelper) 17 { 18 /* 19 * 第二次检查,必须要进行的检查 20 */ 21 if (instance == null) 22 { 23 instance = new Singleton(); 24 } 25 } 26 } 27 return instance; 28 } 29 } 30 }
另外的一个好处是在我们需要访问单例对象时才创建该对象,这种延迟加载的策略也降低了系统的开销。
PS:博客搬家(有改动),原文发布于2012-02-29 http://lukyw.sinaapp.com/2012/02/29/singleton/