【问题标题】:Singleton pattern (Bill Pugh's solution)单例模式(Bill Pugh 的解决方案)
【发布时间】:2011-05-24 11:46:52
【问题描述】:

我正在阅读关于单例模式的 wiki,但我不确定我是否理解这一点:https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom 部分正确。

所以简单来说:为什么 Bill Pugh 的解决方案比上面的例子更好?

是不是因为静态类在实际使用之前没有被VM加载或者类似的东西,所以我们在转向getInstance()方法之前没有创建对象? 此外,该方法是否仅在初始化对象的范围内是线程安全的?

【问题讨论】:

    标签: java design-patterns singleton


    【解决方案1】:

    我认为 Pugh 先生的版本受到高度重视,因为它仅在调用 getInstance() 时执行单例实例化,而不是在加载类(持有 getInstance 方法的类)时。如果你的单例结构做了一些代价高昂的事情,那么这对你来说可能是一个优势。如果您像世界上大多数人一样,他们的单例只是为了避免使用静态方法(并且您还没有转向依赖注入框架),那么我不会为此失眠。

    正如文章所述,Pugh 先生的方法比静态实例变量更懒惰 - 但实际上,如果 Singleton 类被加载,您无论如何都会调用 getInstance 方法。因此,作为一项计算机科学练习,它很有用,但在现实世界中,它的好处是值得商榷的。

    附言我不太关心 Bloch 先生在这里的示例,因为使用枚举会说 My Singleton IS-A 枚举,这对我来说听起来不正确(尤其是从正确地说永远不会实现接口只是为了获取常量)

    【讨论】:

    • 但是如果我的类除了单例之外没有其他方法,那么在调用 getInstance() 之前它不会被实例化?
    • Mr Pugh 示例中的静态类仅在调用 getInstance 时才会加载,因为这是第一次引用它。持有 getInstance 方法的公共类可能只有 getInstance 方法 - 正如我在上面从计算机科学的角度所说的那样,这很有趣,但实际上它与“传统简单方法”的区别很小
    • 我采用了枚举的思维方式,因为该类的实例数量是静态可知的。这就像 Suits 的类示例,其中正好有四个;这里我们有一个只有一个成员的枚举,INSTANCE。尽管我可以理解这种令人困惑的观点。 (然而,这个静态定义的单一实例的“怪异”实际上只是突出了单例模式的弱点。你对一个关于“双吨”的枚举有什么看法,enum DatabaseConnection { LIVE, TEST }?)
    • @Andrzej Doyle 我认为你的观点很重要。我想我看到枚举值中有某种含义时会使用枚举,即这具有功能意义,并且您只希望在系统中使用该值的单一表示。我已经看到诸如 CustomerLogic 或 AccountLogic 之类的类实现为单例(只是为了避免静态方法),在这种情况下,我看不出枚举是合乎逻辑的,即使从技术上讲它适合工作。
    • @planetjones 实际上持有 getInstance() 方法的公共类通常至少有一个实例方法(例如 java.awt.Toolkit)——重要的一点是 getInstance() 被称为 第一.
    【解决方案2】:

    JLS 保证类仅在第一次使用时才被加载(使单例初始化延迟),并且类加载是线程安全的(使 getInstance() 方法也是线程安全的)

    至于为什么是线程安全的

    因为第一次调用getInstance(),JVM会加载holder类。如果另一个线程同时调用 getInstance(),JVM 不会第二次加载 holder 类:它会等待第一个线程完成类加载,并且在 holder 类的加载和初始化结束时,两个线程都会看到正确初始化的持有者类,因此包含唯一的单例实例。

    【讨论】:

    • 嗨,我实际上不明白,它是如何线程安全的。你能解释一下吗?
    • 因为第一次调用getInstance(),JVM会持有持有者类。如果另一个线程同时调用 getInstance(),JVM 不会第二次加载 holder 类:它会等待第一个线程完成类加载,并且在 holder 类的加载和初始化结束时,两个线程都会看到正确初始化的持有者类,因此包含唯一的单例实例。
    【解决方案3】:

    是不是因为静态类不是 由虚拟机在实际加载之前加载 用过

    不仅仅是一个静态类,任何类。类在被引用之前不会被加载。请参阅 JLS - 12.4.1 When Initialization Occurs

    或类似的东西,所以我们不 在我们转向之前创建对象 getInstance() 方法?

    没错。

    该方法也是线程安全的 在初始化的范围内 对象?

    分发引用是线程安全的,所以这个方法总是线程安全的,而不仅仅是在创建时

    【讨论】:

      【解决方案4】:

      解释的关键部分如下:

      嵌套类没有被引用 更早(因此加载没有 早于类加载器)比 调用 getInstance() 的那一刻。 因此,此解决方案是线程安全的 无需特殊语言 构造(即 volatile 或 同步)。

      Bill Pogh 的解决方案提供了懒惰。

      【讨论】:

        【解决方案5】:

        是不是因为静态类不是 由虚拟机在实际加载之前加载 用过或类似的东西,所以我们 在我们转弯之前不要创建对象 到 getInstance() 方法?

        正确。

        该方法是否也仅在初始化对象的范围内是线程安全的?

        它确保只创建一个实例,并且除了对完全初始化的实例的引用之外,没有任何客户端接收到任何内容。

        【讨论】:

          【解决方案6】:

          静态内部类将包含 Singleton 实例。当静态内部类加载到内存中时,它将创建 Singleton 实例。它不需要同步。

          公共类单例{

          private Singleton (){}
          
          private static class SingletonHelper{
              private static final Singleton INSTANCE = new Singleton ();
          }
          
          public static Singleton getInstance(){
              return SingletonHelper.INSTANCE;
          }
          

          }

          【讨论】:

          • 请添加更多详细信息以扩展您的答案,例如工作代码或文档引用。
          猜你喜欢
          • 1970-01-01
          • 2018-11-19
          • 1970-01-01
          • 2013-03-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多