处理 SSI 文件时出错
本文介绍了称为双检锁(Double-Check Locking简称DCL)模式的代码模式,它的工作原理及其在Singleton(单例)模式及Multiton(多例)模式中的应用,并且讨论了DCL模式在Visual Basic.NET和C#语言中的实现。其中Visual Basic.NET的源代码可以在文中看到,C#的源代码在附录中给出。
本文假设读者熟悉Visual Basic.NET或C#的多线程概念、设计模式的基本概念,以及UML基本图标。
DCL模式(Double-Check Locking Pattern)有时又称作双检模式(Double-Check Pattern),只有在多线程的环境中才有用。它是从C语言移植过来的。在C语言里,DCL模式常常用在多线程环境中类的迟实例化(Late Instantiation)里。
DCL模式通常与Factory模式一同使用,用来循环使用产品对象。如果读者熟悉Singleton(Singleton)模式的话,DCL模式可以使用到"懒汉式"的Singleton模式里面,用来提供唯一的产品对象。通过进一步推广,可以使用到Multiton模式和Flyweight模式里面。
从Factory模式谈起 为了解释什么是DCL模式,还是从Factory模式谈起吧。
在下面的类图中,工厂类Factory0有一个共享方法GetInstance()用来提供产品类Product的实例。
 图1、一个由工厂类与产品类组成的系统。 |
Factory0的源代码如下:
Public Class Factory0 Public Shared Function GetInstance() As Product Return New Product() End Function End Class |
显然,只要调用GetInstance()方法就会得到Product类的实例,每一次调用得到的都是新的实例。Product类特别提供了计数的方法,通过调用GetCount()方法就可以得到Product所有实例的总数。
Public Class Product Private Shared count As Integer = 0
Public Sub New() count += 1 System.Console.WriteLine("Product number {0} is created.", count) End Sub
Public Shared Function GetCount() As Integer Return count End Function End Class |
但是如果产品类的实例必须循环使用,而不能无限制创建的话,工厂方法GetInstance()的内容必须改写,以实现必要的循环逻辑。而最简单的循环逻辑,就是重复使用单一的产品类实例。比如下面的源代码就实现了单一产品类实例的逻辑:
Public Class Factory1 Private Shared instance As Product
Public Shared Function GetInstance() As Product If (instance Is Nothing) Then instance = New Product() End If Return instance End Function End Class |
简单得不能再简单了吧?如果已经创建过Product类实例的话,就返还这个实例;反之,就首先创建这个实例,将之记录在案,然后再返还它。
写出这样的代码,本意显然是要保持在整个系统里只有一个Product 的实例;因此才会有 If (instance Is Nothing) Then 的检查。不很明显的是,如果在多线程的环境中运行,上面的代码会有两个甚至两个以上的Product对象被创建出来,从而造成错误。
在多线程环境里,如果有两个线程A和B几乎同时到达 If (instance Is Nothing) Then语句的外面的话,假设线程A比线程B早一点点,那么:
1. A会首先进入If (instance Is Nothing) Then 块的内部,并开始执行New Product()语句。 至此时,instance变量仍然是Nothing,直到线程A的New Product()语句返回并给instance变量赋值。
2. 但是,线程B并不会在If (instance Is Nothing) Then 语句的外面等待,因为此时instance Is Nothing是成立的,它会马上进入If (instance Is Nothing) Then语句块的内部。 这样,线程B会不可避免地执行instance = New Product()的语句,从而创建出第二个实例来。
3. 下面,线程A的instance = New Product()语句执行完毕,instance变量得到了真实的对象引用, (instance Is Nothing)不再为真。第三个线程不会在进入If (instance Is Nothing) Then语句块的内部了。
4. 紧接着,线程B的instance = New Product()语句也执行完毕,instance变量的值被覆盖。但是第一个Product对象被线程A引用的事实不会改变。