.NET设计模式--单例的深入研究
单例是一个耳熟能详的设计模式,它的应用场景就是在需要一个类只能拥有一个实例的情况下使用,在许多方面有较好地应用。有时候,需要加载一些数据片断,如公共的数据访问连接字符串,一段公共的文件流或者是需要全局缓存的数据。
一般而言,单例分成饿汉式与懒汉式两种,另外还有一种注册式单例,是用来解决继承问题的。
在蝈蝈俊.NET的<<[整理]单件模式(Singleton)的延迟初始化(Lazy Initialization)和(Early initialization) >>一文中提供了单件模式的两种形式,因为蝈蝈俊.NET给出的只是C#代码,也许是依照JAVA的代码形式进行表述的,作为伪代码无可厚非,但是,如果是作为.NET下的C#实现,肯定是有问题的,所以难免会有给人造成误解之嫌。在该文的回复中,Chainet老兄 给出了自己的文章的链接:<<静态构造函数与Singleton模式在C#中的实现 >>,并指出了"CLR在框架上已经保证了静态构造函数在多线程环境下的同步问题,所以的volatile在Early initialization中是没有必要的。"
在MSDN中的文章<<检查表:托管代码的安全检查>>中,"托管代码检查指南-->线程处理"里明确指出了“在多线程应用程序代码的静态类构造函数中,对线程进行同步”及“在静态类构造函数中,对线程进行同步。”两条检查规范,可见,CLR并没有对静态构造函数在多线程环境下进行自动地同步处理。
如下代码实现:
{
private static readonly Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
实际上这是一个非线程安全的懒汉式单例。
因为.NET中的静态实例的处理,不是在软件启动时进行处理,而是要当包含静态访问的字段、方法、属性或其它什么静态的玩意的类被第一次访问时,在构建实例之前进行创建,也就是说,是临时发觉要用之前才进行创建的。这意味着,如果.NET的框架没有提供一个静态函数初始化的线程安全的处理的情况下,该初始化是并非线程安全的。即使是用了readonly也无济于事:当构建此实例的时间超出了实例创建出来并在框架中进行注册的所消耗的时间时,多线程中假设两个线程同时并发,第一个线程在系统进行静态实例创建时,第二线程也对此类访问,中间的时间差造成了CLR并不知道第一个静态实例正在创建中,那么就会发生了再次进行静态实例创建,这就会造成两个实例的出现,但因为声明了readonly,结果就是会抛出一个异常。
在MSDN中文网上的<<在C# 中实现 Singleton>>一文中,虽然没有明确指出这一点,但有如下的代码及描述:
{
private static readonly Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
"在此策略中,将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记为 sealed 以阻止发生派生,而派生可能会增加实例。有关将类标记为 sealed 的利与弊的讨论,请参阅 [Sells03]。此外,变量标记为 readonly,这意味着只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。
该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。
由于Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用所引用之前,不会发生实例化。因此,与 Design Patterns 形式的 Singleton 一样,该解决方案实现了懒实例化属性的一种形式。
这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。"
.NET中提供了轻便的单例实现方式,但要注意的是,这个轻便地实现并不是饿汉式的,而是懒汉式的,尽管它写法看起来很像饿汉式的,这里关键的区别就是在,一般性的轻便实现,是基于非线程安全的单例类,而如果要实现线程安全的单例类,在.NET就要使用双锁模式。