| | | | | | | [文章信息] | | | 作者: | Mark Townsend | | 时间: | 2004-08-02 | | 出处: | MSDN开发精选6月刊 | | 责任编辑: | 方舟 | |
| [文章导读] | | | 在本文中,我们将讨论Singleton模式,它包含在创造性模式系列中 | |
| |
|
| | | |
|
|
|
|
|
逻辑模型
Singleton模型非常简单直观。(通常)只有一个Singleton实例。客户端通过一个已知的访问点来访问Singleton实例。在这种情况下,客户端是一个需要访问唯一Singleton实例的对象。图1以图形方式显示此关系。
物理模型
Singleton模式的物理模型也是非常简单的。但是,随着时间的推移,实现Singleton的方式也略有不同。让我们看一下原始的GoFSingleton实现。图2显示按设计模式所定义的原始Singleton模式的UML模型。
我们看到的是一个简单的类图表,显示有一个Singleton对象的私有静态属性以及返回此相同属性的公共方法Instance()。这实际上是Singleton的核心。还有其他一些属性和方法,用于说明在该类上允许执行的其他操作。为了便于此次讨论,让我们将重点放在实例属性和方法上。
客户端仅通过实例方法来访问任何Singleton实例。此处没有定义创建实例的方式。我们还希望能够控制如何以及何时创建实例。在OO开发中,通常可以在类的构造函数中最好地处理特殊对象的创建行为。这种情况也不例外。我们可以做的是,定义我们何时以及如何构造类实例,然后禁止任何客户端直接调用该构造函数。这是在Singleton构造中始终使用的方法。让我们看一下设计模式中的原始示例。通常,将下面所示的C++Singleton示例实现代码示例视为Singleton的默认实现。本示例已移植到很多其他编程语言中,通常它在任何地方的形式与此几乎相同。
C++ Singleton示例实现代码
//Declaration class Singleton{ public: static Singleton* Instance(); protected: Singleton(); private: static Singleton* _instance; }
// Implementation Singleton* Singleton::_instance = 0;
Singleton* Singleton::Instance() { if (_instance == 0) { _instance = new Singleton; } return _instance; } | 让我们先花点时间分析一下此代码。该简单类有一个成员变量,此变量是指向该类自身的指针。注意,构造函数是受保护的,并且只有公共方法才是实例方法。在实例方法实现中,有一个控制块(if),它检查成员变量是否已初始化,如果没有的话,则创建一个新实例。控制块中这种惰性初始化意味着仅在第一次调用Instance()方法时初始化或创建Singleton实例。对于很多应用程序,这种方法效果很好。但对于多线程应用程序,这种方法证明具有潜在危险的副作用。如果两个线程同时进入控制块,则可能会创建该成员变量的两个实例。要解决这一问题,您可能想只将重要部分放在控制块周围以确保线程安全。如果您这样做,则将对实例方法的所有调用进行序列化处理,并且可能会对性能产生不利影响(取决于应用程序)。正是由于这个原因,创建了此模式的另一个版本,它使用某种称为双重检验机制的功能。下一个代码示例显示使用Java语法的双重检验锁定。
使用Java语法的双重检验锁定Singleton代码
//C++ port to Java class Singleton { public staticSingletonInstance() { if (_instance == null) { synchronized (Class.forName("Singleton")) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } protected Singleton() {} private staticSingleton_instance = null; } | 在使用Java语法的双重检验锁定Singleton代码示例中,我们直接将C++代码移植到Java代码,以便利用Java关键部分块(已同步)。主要差别是不再有单独的声明和实现部分,没有指针数据类型,并且采用了新的双重检验机制。双重检验发生在第一个IF块上。如果成员变量为空,则执行进入关键部分块,该块再次双重检验该成员变量。仅在通过此最终测试后,才会实例化该成员变量。一般来说,两个线程无法使用这种方法创建两个类实例。另外,因为在第一次检查时没有出现线程阻塞,所以对此方法的大多数调用不会由于必须进入锁定而导致性能下降。目前,在实现Singleton模式时,很多Java应用程序中都广泛使用这种方法。
这种方法很巧妙,但也有瑕疵。某些优化编译器可以将惰性初始化代码优化掉或对其重新进行排序,并且会重新产生线程安全问题。
|
|
|
|
|
|
|
|
|