单例模式估计是所有设计模式中最简单的一种了,也是应用得最多的一种。
通常情况下,之所以要使用单例,是因为创建一个实例需要耗费大量的资源,因此,通常单例会采用延迟创建的模式。即在访问单例实例时才进行创建:
private static Singleton _instance;
public Singleton Instance
{
get { return _instance ?? (_instance = new Singleton()); }
}
但这种延迟创建,在多线程环境下的是不安全的,在多个线程同时执行到 ?? 表达式的时候,他们可能都判断 _instance 为 null, 认为实例未创建,从而各自创建一个实例。
SO,一种解决办法是加锁,并且进行 Double Check 双重检查:
private volatile static Singleton _instance;
private static object _syncLock = new object();
public Singleton Instance
{
get
{
if (_instance == null )
{
lock (_syncLock)
{
if (_instance == null) _instance = new Singleton();
}
}
return _instance;
}
}
这是 MS 的官方写法,这也是一个标准的 Double Check 写法,也就是在加锁前后都检查 _instance == null。这个创建的过程逻辑已经复杂到足够让一些人蛋疼了。
但是理论上来说,Double Check 这种写法也是不安全的。MS 这样写是否线程安全,和 Java 的类似代码一样,还是要取决于编译器如何实现对象创建的逻辑,假如编译创建对象的逻辑为:
- temp = 为要创建的对象分配内存
- 调用对象的构造函数
- instance = temp
那么应该没什么问题。但是编译器的逻辑是这样:
- instance = 为要创建的对象分配内存
- 调用对象的构造函数
那么这个 Double Check 同样是线程不安全的,其中一个线程执行完第一步时 _instance 已经不是 null 了,但是第二步还未执行,对象还未初始化,其他线程却可通过 _instance 开始调用对象的某个方法,这时候,由于对象的所对应的内存还未完成初始化,这可能导致一个非常严重的指针泄露问题。
SO,公布最后答案:最安全并且最简洁的单例延迟创建模式应该如下所示,这是 Google公司的工程师 Bob Lee 创造的写法,对于 C# 和 Java 都适用。简单而高效,确实介于牛A和牛C之间。
public class Singleton { private static class Holder { public static readonly Singleton _instance = new Singleton(); } public static Singleton Instance { get { return Holder._instance; } } }