单例模式是广为流传的设计模式中的一种。本质上,单例模式是一个只允许创建一个实例,并提供对这个实例简单的访问途径的类。一般而言,单例模式在创建实例时 不允许传递任何参数-否则不同参数导致不同的实例创建,就会出现问题!(如果同一个实例可以被同参的不同请求所访问,那么工厂模式会更适合。)这篇文章只 针对无参创建的请求进行讨论。
典型的,单例模式的应用往往是延后创建的(created lazily)---只有在第一次被用到的时候才会被创建。在C#中有实现单例模式有很多种方法。我将在这里一一展现给大家,从最常见的、线程不安全的到延后创建的、线程安全的、再到简洁高效的版本。
注意在下面的代码中,我忽略了所有私有域,因为私有域是默认的类的成员。In many other languages such as Java, there is a different default, and private should be used.
所有的这些实现都有以下四个特征:
- 只有一个构造函数,而且是私有的,不带参数的。这是为了防止其他类对其实例化(这和模式本身有冲突)。同时也防止了子类化--如果一个单例能被子类化 一次,就能被子类化两次,而如果每个子类可以创建一个实例,这与模式本身又产生了冲突。如果你(遇到这样的情况):只有在运行期才能知道实际的类型,因此 你需要一个父类的单例,你可以使用工厂模式。
- 类是密封的。这并不是必须的,严格的说,即如上一点所说的原因,可以提高JIT(Just-In-Time , 运行时编译执行的技术)的效率。
- 一个静态变量用来保存单例的引用。
- 一个用以访问单例引用的公用静态方法。
注意:所有这些实现都用到一个公用静态属性Instance,作为访问实例的方法。当然都可以替换为方法,这对线程安全和性能都没有影响。
版本1-非线程安全
// Bad code! Do not use!
public sealed class Singleton
{
static Singleton instance=null;
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}
如 上提示,上面的代码是非线程安全的。两个线程可能同时判断“if (instance==null)”,发现为TRUE,于是都创建了实例,这又违背了单例模式。注意:事实上在表达式反应前实例已经被创建了,内存模型并 不保证新的实例的值被其他的线程看到,除非对应的内存屏障已经通过了。(CPU越过内存屏障后,将刷新自已对存储器的缓冲状态,这样其他线程才能同步自己 的copy)
版本2 - 简单的线程安全
public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
lock (padlock)
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}
}
这个实现是线程安全的。线程首先对共用的对象进行锁定,然后判断实例是否在之前已经创建。这里要小心内存屏障问题(在锁定的时候确保所有的读操作发生在所获 得之后,在解锁的时候确保所有的写操作发生在锁释放之前),确保只有一个线程创建了实例(因为在同一时刻只有一个线程可以在执行那段代码--到了第二个线 程进入的时候,第一个线程已经完成了实例的创建,这样表达式返回false)。
不幸的是,由于在每次访问的时候都要进行锁定,所以影响了性能。(这对于多 线程并发的高性能要求的应用显得尤为重要)。注意有些该版本的实现对typeof(Singleton)进行锁定,但我是对类的私有静态变量进行锁定。
对那些可被其他类访问的对象进行锁定或对类型进行锁定会导致性能问题甚至引起死锁。这是一个地雷-任何地方都有可能发生,只有对本就为锁定而创建的对象进行 锁定或是那些因为某些目的而被锁定的文档才是安全的。通常这些对象都是私有的。这样有助于写出线程安全的程序。
版本3 -- 用双重检测机制的线程安全
// Bad code! Do not use!
public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
lock (padlock)
{
if (instance==null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}