科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件设计模式之Singleton(单态模式)

设计模式之Singleton(单态模式)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

讨论 Singleton 设计模式(指示如何以及何时创建对象的创造性模式)及其在 Microsoft .NET 框架中的有效使用。

作者:中国IT实验室 来源:中国IT实验室 2007年8月26日

关键字:

  • 评论
  • 分享微博
  • 分享邮件

 我们看到的是一个简单的类图表,显示有一个 Singleton 对象的私有静态属性以及返回此相同属性的公共方法 Instance()。 这实际上是 Singleton 的核心。 还有其他一些属性和方法,用于说明在该类上允许执行的其他操作。 为了便于此次讨论,让我们将重点放在实例属性和方法上。

 客户端仅通过实例方法来访问任何 Singleton 实例。 此处没有定义创建实例的方式。 我们还希望能够控制如何以及何时创建实例。 在 OO 开发中,通常可以在类的构造函数中最好地处理特殊对象的创建行为。 这种情况也不例外。 我们可以做的是,定义我们何时以及如何构造类实例,然后禁止任何客户端直接调用该构造函数。 这是在 Singleton 构造中始终使用的方法。 让我们看一下 Design Patterns 中的原始示例。 通常,将下面所示的 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 static Singleton Instance() {
   if (_instance == null) {
    synchronized (Class.forName("Singleton")) {
     if (_instance == null) {
     _instance = new Singleton();
    }
   }
  }
return _instance;
}
protected Singleton() {
}
private static Singleton _instance = null;
} 

 在使用 Java 语法的双重检验锁定 Singleton 代码示例中,我们直接将 C++ 代码移植到 Java 代码,以便利用 Java 关键部分块(已同步)。 主要差别是不再有单独的声明和实现部分,没有指针数据类型,并且采用了新的双重检验机制。 双重检验发生在第一个 IF 块上。 如果成员变量为空,则执行进入关键部分块,该块再次双重检验该成员变量。 仅在通过此最终测试后,才会实例化该成员变量。 一般来说,两个线程无法使用这种方法创建两个类实例。 另外,因为在第一次检查时没有出现线程阻塞,所以对此方法的大多数调用不会由于必须进入锁定而导致性能下降。 目前,在实现 Singleton 模式时,很多 Java 应用程序中都广泛使用这种方法。 这种方法很巧妙,但也有瑕疵。 某些优化编译器可以将惰性初始化代码优化掉或对其重新进行排序,并且会重新产生线程安全问题。 有关更深入的解释,请参阅 "The Double-Check Locking is Broken" Declaration。

 另一种试图解决此问题的方法可能是,在成员变量声明中使用 volatile 关键字。 这应该告诉编译器不要对代码重新排序,并且放弃优化。 目前,这是唯一建议的 JVM 内存模型,并且不会立即解决该问题。

 实现 Singleton 的最好方法是什么? 最终(而不是碰巧),Microsoft .NET 框架解决了所有这些问题,从而更易于实现 Singleton,却不会产生我们目前讨论的不利副作用。 .NET 框架以及 C# 语言允许我们在必要时通过替换语言关键字,将上述的 Java 语法移植到 C# 语法。 因此,Singleton 代码变为以下内容:

以 C# 编码的双重检验锁定

// Port to C#
class Singleton {
 public static Singleton Instance() {
   if (_instance == null) {
   lock (typeof(Singleton)) {
    if (_instance == null) {
     _instance = new Singleton();
    }
   }
  }
return _instance;
}
protected Singleton() {
}
private static volatile Singleton _instance = null;

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章