从本质上看,单例模式只允许被其自身实例化一次,且向外部提供了一个访问该实例的接口。
C#中实现单例有很多种方法,本文将按顺序介绍非线程安全、完全懒汉式、线程安全和低/高性能集中版本。
第一种: 非线程安全
/// <summary> /// Bad code! Do not use! /// </summary> public sealed class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if (instance == null) { instance = new Singleton(); } return instance; } } }
该版本在多线程下是不安全的,会创建多个实例,请不要在生产环境中使用!
因为如果两个线程同时运行到if(instance==null)判断时,就会创建两个实例,这是违背单例模式的初衷的。实际上在后面那个线程进行判断是已经生成了一个实例,但是对于不同的线程来说除非进行了线程间的通信,否则它是不知道的。
第二种: 简单的线程安全
public sealed class Singleton2 { private static Singleton2 instance = null; private static readonly object obj = new object(); private Singleton2() { } public Singleton2 Instance { get { lock (obj) { if (instance == null) { instance = new Singleton2(); } return instance; } } } }
该版本是线程安全的。通过对一个线程共享的对象进行加锁操作,保证了在同一时刻只有一个线程在执行lock{}里的代码。当第一个线程在进行instance判断或创建时,后续线程必须等待直到前一线程执行完毕,因此保证了只有第一个线程能够创建instance实例。
但不幸的是,因为每次对instance的请求都会进行lock操作,其性能是不佳的。
需要注意的是,这里使用了一个private static object变量进行锁定,这是因为当如果对一个外部类可以访问的对象进行锁定时会导致性能低下甚至死锁。因此通常来说为了保证线程安全,进行加锁的对象应该是private的。
第三种: 使用双重检查锁定尝试线程安全
/// <summary> /// Bad code ! Do not use! /// </summary> public sealed class Singleton3 { private static Singleton3 instance = null; private static object obj = new object(); private Singleton3() { } public static Singleton3 Instance { get { if (instance == null) { lock (obj) { if (instance == null) { instance = new Singleton3(); } } } return instance; } } }
第四种: 不完全懒汉式,但不加锁的线程安全
public sealed class Singleton4 { private static readonly Singleton4 instance = new Singleton4(); /// <summary> /// 显式的静态构造函数用来告诉C#编译器在其内容实例化之前不要标记其类型 /// </summary> static Singleton4() { } private Singleton4() { } public static Singleton4 Instance { get { return instance; } } }
这个版本是的实现非常的简单,但是却又是线程安全的。C#的静态构造函数只有在当其类的实例被创建或者有静态成员被引用时执行,在整个应用程序域中只会被执行一次。使用当前方式明显比前面版本中进行额外的判断要快。
当然这个版本也存在一些瑕疵:
- 不是真正意义上的懒汉模式(需要的时候才创建实例),若单例类还存在其他静态成员,当其他类第一次引用这些成员时便会创建该instance。下个版本实现会修正这个问题;
- 只有.NET中才具有beforefieldinit特性,即懒汉式实现。且在.Net 1.1以前的编译器不支持,不过这个现在来看问题不大;
第五种: 完全懒汉实例化
public sealed class Singleton5 { private Singleton5() { } public static Singleton5 Instance { get { return Nested.instance; } } private class Nested { //Explicit static constructor to tell C# compiler //not to mark type as beforefieldinit static Nested() { } internal static readonly Singleton5 instance = new Singleton5(); } }
该版本看起来稍微复杂难懂,其实只是在写法上实现了上一版本的瑕疵,通过内嵌类的方式先实现了只有在真正应用Instance时才进行实例化。其性能表现与上一版本无异。