Visual Basic、NET实现双检锁(DCL)模式(一)
本文介绍了称为双检锁(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

代码清单1、Factory0类的源代码

  显然,只要调用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

代码清单2、产品类Product的源代码

  但是如果产品类的实例必须循环使用,而不能无限制创建的话,工厂方法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

代码清单3、工厂类Factory1的源代码

  简单得不能再简单了吧?如果已经创建过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引用的事实不会改变。

  这时,线程A和B各自拥有一个独立的Product对象,而这是错误的。为了能够直观地看到程序执行的结果,可以运行下面的客户端代码: