【问题标题】:Simplified Singleton pattern in JavaJava中的简化单例模式
【发布时间】:2011-08-23 19:22:48
【问题描述】:

实现单例模式的默认方式是:

class MyClass {
  private static MyClass instance;
  public static MyClass getInstance() {
    if (instance == null) {
      instance = new MyClass();
    }
    return instance;
  }
}

在一个旧项目中,我试图简化写作:

class MyClass {
  private static final MyClass instance = new MyClass();
  public static MyClass getInstance() {
    return instance;
  }
}

但它有时会失败。我只是不知道为什么,我做了默认的方式。 今天做一个SSCCE发到这里,我已经意识到代码可以工作了。

所以,我想知道意见.. 这是一个偶然的失败代码吗? 第二种方法是否有可能返回null? 我要疯了吗?

-- 虽然我不知道是否是每个案例的正确答案,但@Alfred 的答案非常有趣: 我还想指出,单身人士正在测试噩梦,根据大佬的说法,你应该使用谷歌的依赖注入框架。

【问题讨论】:

  • “我疯了吗?” - 不。但如果你是,我拥有 ACME Purple Elephant Repellant (tm) 的全球分销权。 :-)
  • 这是我见过的最好的单例实现(在方括号之间):[ ]。
  • 以什么方式失败了?你看到的错误是什么?出了什么问题?
  • 我看不出与stackoverflow.com/questions/70689/…的区别
  • 你把例子倒过来了吗?第一个示例很可能返回 null - 它不是线程安全的。我真的不明白第二个如何返回 null

标签: java design-patterns singleton


【解决方案1】:

推荐(Effective Java 2nd ed)的方式是做“枚举单例模式”:

enum MyClass {
    INSTANCE;

    // rest of singleton goes here
}

这里的关键见解是枚举值是单实例的,就像单例一样。所以,通过创建一个单值枚举,你只是让自己成为了一个单例。这种方法的美妙之处在于它是完全线程安全的,而且它还可以防止任何类型的允许人们创建其他实例的漏洞。

【讨论】:

  • 太可怕了。仅仅因为所有枚举都是单例并不意味着每个单例都必须是枚举。创建一个不是枚举的单例作为枚举是可怕的误导。
  • @Software Monkey:是什么让单例“不是枚举”?你在哪里画线? 1 个值的枚举(或者甚至是没有值的枚举,这是合法的顺便说一句)与任何其他枚举一样好,就像 1 或 0 值的元组/序列/列表/向量一样好。
  • 这是一个概念性的东西。枚举是一系列类似标志的标识符,而不是通用对象。
  • @Software Monkey - 与 Java 语言设计者一起讨论名称“enum”或 Joshua Bloch 的模式。这不是不使用 Java 中最好的 Singleton 模式版本(1.5 后)的好理由(也不是说通常有充分的理由使用 Singleton 模式......)
  • @testSubject528491:如果你的单例必须扩展一个真正的类(而不是实现一个接口),那么你的设计就会被破坏。 (是的,我认为ReaderWriterInputStreamOutputStream 等的设计已经损坏。它们应该都是接口。)
【解决方案2】:

第一个解决方案(我相信)不是线程安全的。

第二个解决方案(我相信)是线程安全的,但如果您有复杂的初始化依赖关系,其中 MyClass.getInstance()MyClass 静态初始化完成之前被调用,则可能无法工作。这可能就是您看到的问题。

这两种解决方案都允许某人创建您的(名义上的)单例类的另一个实例。

更强大的解决方案是:

class MyClass {

  private static MyClass instance;

  private MyClass() { }

  public synchronized static MyClass getInstance() {
    if (instance == null) {
      instance = new MyClass();
    }
    return instance;
  }
}

在现代 JVM 中,只要不存在对锁的争用,获取锁的成本微乎其微。

编辑 @Nate 质疑我关于静态初始化顺序的陈述可能会导致问题。考虑以下(病态)示例:

public ClassA {
    public static ClassB myB = ClassB.getInstance();
    public static ClassA me = new ClassA();
    public static ClassA getInstance() {
        return me;
    }
}

public ClassB {
    public static ClassA myA = ClassA.getInstance();
    public static ClassB me = new ClassB();
    public static ClassB getInstance() {
        return me;
    }
}

这两个类有两种可能的初始化顺序。两者都会导致在执行方法的类静态初始化之前调用静态方法。这将导致ClassA.myBClassB.myA 被初始化为null

在实践中,静态变量之间的循环依赖没有这么明显。但事实仍然是,如果存在循环依赖:1) Java 编译器将无法告诉您它,2) JVM 不会告诉您它。相反,JVM 会默默地选择一个初始化顺序,而不会“理解”你正在尝试做的事情的语义......可能会导致一些意外/错误。

EDIT 2 - 在JLS 12.4.1 中描述如下:

如第 8.3.2.3 节中的示例所示,初始化代码不受限制的事实允许构造示例,其中当类变量的值仍具有初始默认值时,在其初始化表达式为评估,但这样的例子在实践中很少见。 (这样的例子也可以构造为实例变量初始化;参见第 12.5 节末尾的例子)。这些初始化器中提供了语言的全部功能;程序员必须小心谨慎。 ...

【讨论】:

  • "其中 MyClass.getInstance() 在 MyClass 静态初始化完成之前被调用。这可能是您看到的问题。 - 不可能。静态初始化程序在第一次引用该类时运行。调用静态方法时会引用该类。静态初始化将在调用静态方法之前完成。
  • @Nate - 你检查过myAmyB 的值吗??????这就是问题所在。
  • 啊 - 好的,是的,我明白你的意思了。很抱歉之前对此表示怀疑,但感谢您更多地描述它以便我理解,并感谢您在这个奇怪的“陷阱”上启发我。看起来你也可以通过在类的顶部创建一个静态块并在那里调用 getInstance() 来导致这种情况。
【解决方案3】:

第二个例子更可取,因为第一个不是线程安全的(正如 cmets 中所指出的那样)。第一个示例使用了一种称为lazy instantiation(或延迟初始化)的技术,它确保除非实际需要,否则不会创建 Singleton 实例。由于 Java 处理类加载和静态实例变量初始化的方式,这在 Java 中并不是真正需要的。

【讨论】:

  • @tony:你可以给它一个AtomicReference,让它成为线程安全的。 :-P(或者,如果绝对不能多次构造该类,则同步 getInstance 方法。)
  • 会 +1 这个,除非声称非线程安全版本是“OK”。
  • @Chris: 或者...只是同步 getInstance() 方法。
【解决方案4】:

我还想指出,singletons 正在测试噩梦,根据大佬的说法,您应该使用 google 的 dependency injection framework

【讨论】:

    【解决方案5】:

    记住,你需要声明一个私有构造函数来保证单例属性。

    第二种情况可能更简单

    class MyClass {
      public static final MyClass instance = new MyClass();
    
      private MyClass() {
          super()
      }
    }
    

    `

    【讨论】:

      【解决方案6】:

      正如其他人所指出的,第一个不是线程安全的。不要打扰它,因为第二个非常好,并且只有在引用 MyClass 时才会实例化对象。此外,它使参考最终更好地表达了意图。

      只要确保声明

      private static final MyClass INSTANCE = new MyClass();
      

      是类中的第一个静态声明,以避免在初始化之前调用 getInstance() 或 INSTANCE 的风险。

      【讨论】:

        【解决方案7】:

        不要忘记 SingletonHolder 模式。请参阅this SO 问题。

        【讨论】:

          【解决方案8】:

          我不知道你的问题的答案,但这是我可以如何构建相同的东西。

          class MyClass {
            private static MyClass instance;
          
            static {
              instance = new MyClass();
            }
          
            private MyClass() { }
          
            public static MyClass getInstance() {
              return instance;
            }
          }
          

          【讨论】:

          • 不做private static final MyClass instance = new MyClass();的原因是什么?
          • @Carlos 除了风格之外,我没有其他理由知道。当您阅读代码时,静态部分的用途非常明显。 final 总是很棒,但有时我不喜欢将初始化和字段声明放在一起。
          • @Jonathan:我认为我的行比将它分布在三行更明显,至少对于这样简单的代码(品味问题?!)......并使用final也有助于那个方向;你仍然可以在你的代码中使用final,编译器应该足够聪明以允许它(我怀疑它会产生相同的字节码)。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-02-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多