单例模式:就是指一个类里面只一个实例,并提供一个全局的访问点。在C#里,单例模式的实现方便有很多种。个人的见解是,由于整个类只有一个实例对象,因此,必须做到这人实例对象只能由类它本身来管理,即由该类来创建与销毁这个实例。
以下是五种创建单例的做法。虽然它们表面上都实现了由类本身管理这个实例,但是却未必都是正确的做法。
方法一:
public class Singleton
{
private static Singleton _instance;
private Singleton()
{
}
public static Singleton GetInstance()
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
说明:以上的实现在单线程的情况上基本正确,但是却几乎不可能在多线程的情况下正确运行。其原因是“红色”部分的几行语句并不是一个原子操作,仅从C#而言,就已经是由多个语句组成的;如果再变成IL代码或者是底层的汇编代码,那将是更多条语句。因此,这里无法实现在“红色”部分代码的原子操作。所以在多线程的访问下有可能会失败。
方法二:
基于”方法一“,为了确保判断实例与创建实例的“原子操作”,那最简单的做法就是加锁。
public class Singleton
{
private static Singleton _instance;
private static object obj = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
lock (obj)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
return _instance;
}
}
说明:看起来”方法二“已经修复了”方法一“的bug了。但是,难道每一次调用:“GetInstance()"方法是我都不得不加锁吗?要知道,锁可不是说加就加的,是有代价的。
方法三:
继续改进”方法二“吧。我们的目标是不要每一次请求”GetInstance()“就加锁,其实,加锁只有在_instance未实例话时需要,所以:
public class Singleton
{
private static Singleton _instance;
private static object obj = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
if (_instance == null)
{
lock (obj)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
说明:经过了”方法三“的处理之后,”红色“部分的代码只会完整地执行一次,而以后的请求将不会再加锁了。因此,加锁的次数将会大大减少,成功的加锁只会有一次。
方法四:
为什么一定要加锁呢,不如:
public class Singleton
{
private static Singleton _instance = new Singleton();
private Singleton()
{
}
public static Singleton GetInstance()
{
return _instance;
}
}
说明:充分利用类的静态构造函数吧。静态字段是由类的静态构造函数负责实例化。在多线程的情况下,CLR确保了每个AppDomain中只对静态构造函数执行一次。在多线程的环境下,CLR为每个想要调用静态构造函数的方法提供一个互拆线程同步锁。因此,只有一个线程可以获得这个锁,并执行静态构造函数对字段进行实例化。其他的线程处于阻塞的状态。等第一个线程释放锁时,其他线程被唤醒,但是发现构造器的代码已经执行过了,因此,不再继续执行,而是直接返回。
方法五:
但是,这样一来就不是”按需分配“了,如果这个Singleton实例的初始化需要大量的时间和资源呢,而且又无法保证这个Singleton实例一定会被进程使用。这样不就是浪费资源吗?
public class Singleton
{
private Singleton()
{
}
public static Singleton GetInstance()
{
return Nest.Instance;
}
class Nest
{
private static Singleton _instance = new Singleton();
private Nest()
{
}
public static Singleton Instance
{
get { return _instance; }
}
}
}
说明:
和”方法四“类似,我们可以保证这个单例模式在多线程的情况下是可正确运行的,因为Nest 类只有Singleton可访问,并且Singleton类是不能被继承的,因为它的构造方法为private。而且我们也做到了”按需分配“。
如果Singleton.GetInstance()方法没有被调用,也就是没有发起对Nest的静态构造函数的调用,因此,_instance实例不会被创建。
只有在调用Singleton.GetInstance()方法被调用之后,才会调用Nest类的静态构造函数(而且这个静态构造函数的调用 对多线程也是需要加互斥同步锁的),所以可以确保只对_instance实例创建一次。
各位看官有何想法?