4

我正在探索单例设计模式,我开发了一个类......

public class SingletonObject {
  private static SingletonObject ref;       
  private SingletonObject () { //private constructor
  }     
  public static synchronized SingletonObject getSingletonObject() {
    if (ref == null)
      ref = new SingletonObject();
    return ref;
  } 

  public Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException ();
  }
}

但是同步非常昂贵,所以我转向热切创建实例的新设计,而不是懒惰创建的实例。

public class Singleton {
  private static Singleton uniqueInstance = new Singleton();
  private Singleton() {
  }
  public static Singleton getInstance() {
    return uniqueInstance;
  }
}

但是请告诉我第二种设计比以前的设计有什么优势..!!

4

6 回答 6

5

Josh Bloch 建议使用枚举:

   public enum Foo {
       INSTANCE;
   }

有关解释,请参阅他在 Google I/O 2008 上的Effective Java Reloaded演讲。

总之:

“这种方法在功能上等同于公共字段方法,只是它更简洁,免费提供序列化机制,并且即使面对复杂的序列化或反射攻击,也能提供防止多次实例化的铁定保证。虽然这种方法已经尚未被广泛采用,单元素枚举类型是实现单例的最佳方式。”

于 2012-04-20T17:40:33.510 回答
2

正如您所说,第二种解决方案避免了同步成本。它也更简单、更干净,因此更易于阅读和维护。不过它有一个小问题:你错过了 的final限定符private static Singleton uniqueInstance,这意味着它可能无法保证在并发环境中是线程安全的(尽管在这个具体的情况下,我认为这不会在现实生活中造成任何明显的问题。 ..但最好在线程安全方面保持安全)。幸运的是,这很容易解决。

它的另一个缺点是,一旦Singleton引用了类,就会创建单例对象,即使它从未实际使用过。如果创建成本很高,这可能是一个问题。可以使用Initialization-on-demand Holder习惯用法来避免这种情况。

于 2012-04-20T17:36:44.793 回答
1

您的第二个设计更好,因为它更简洁且更易于阅读。此外,正如您所提到的,它避免了每次您希望使用单例时进行同步的成本。

第二种设计的一个缺点是,即使您从不使用它,也会产生实例化单例的内存和 CPU 成本。

于 2012-04-20T17:35:32.693 回答
1

急切实例化与延迟初始化

您的第二个设计使用急切实例化而不是延迟初始化。这不一定是更好或更坏,这取决于哪个适合您的应用程序。

一般来说,如果出现以下情况,最好使用延迟初始化:

  • 如果您的应用程序有可能不需要创建您的类的实例
  • 如果实例化你的类很昂贵,你宁愿把操作推迟到尽可能晚

线程安全和性能

第二个设计的另一个优点是它的性能更高。在多线程环境中,您的第一个设计将要求每个线程在获取实例之前获取锁,即使它已经被实例化。您可以通过使用双重检查锁定Bill Pugh 方法来解决此问题。

枚举方式

与您的两种设计不同的方法是Enum 方式,它使用具有单个值的 Enum。由于枚举可以具有方法和成员变量,因此您可以模仿普通类的行为。这是一种创建单例的好方法,它是由 Joshua Bloch 推荐的。

于 2012-04-20T17:40:36.333 回答
0

您还应该使变量指向您的单例final

public class Singleton {
    private static final Singleton uniqueInstance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return uniqueInstance;
    }
}

这个实现让 ClassLoader 实例化单例的实例,提供线程安全。另一种模式使用enum,但我个人认为该实现是代码异味。

于 2012-04-20T17:38:02.000 回答
0

几点注意事项:

  1. 您的第一个示例在多线程场景中无法按预期工作。您的 ref 应该是volatile可变的,并且需要使用锁定进行仔细检查。

  2. 您的第二个示例没有额外synchronization费用。但是你可以Lazy Singleton有效地实现而不是急切的 Singleton。

有关详细信息,请参阅以下 SE 问题:

为什么在这个双重检查锁定示例中使用 volatile

于 2016-07-07T10:59:44.573 回答