维基百科的单例模式实现

我指的是Bill Pugh在维基百科上的Singleton Pattern的解决方案 :

public class Singleton { // Private constructor prevents instantiation from other classes private Singleton() {} /** * SingletonHolder is loaded on the first execution of Singleton.getInstance() * or the first access to SingletonHolder.INSTANCE, not before. */ private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } 

他们在这里提到:

与调用getInstance()的时刻相比,内部类不会被引用(因此不会被类加载器加载)。 因此,该解决方案是线程安全的,无需特殊的语言结构(即volatilesynchronized )。

但是,2个线程是否有可能同时调用getInstance() ,这会导致创建两个单例实例? 在这里synchronized使用是否安全? 如果是,那么它应该在代码中使用?

请参阅同一部分中链接的“初始化按需持有者习惯用语”中的“ 工作原理” 。

简而言之,这是第一次调用getInstance()时会发生的情况:

  1. JVM看到了之前从未见过的SingletonHolder引用。
  2. 初始化 SingletonHolder暂停执行。 这包括运行任何静态初始化,其中包括单例实例。
  3. 执行恢复。 同时调用getInstance()任何其他线程都会看到SingletonHolder已经初始化。 Java规范保证类初始化是线程安全的。

JLS保证JVM不会初始化实例,直到有人调用getInstance(); 这将是线程安全的,因为它会在SingletonHolder的类初始化期间发生。

但是, 从Java 5开始,首选方法涉及Enum

 // Enum singleton - the preferred approach public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } } 

参考: 在Java中实现单例模式

以下是维基百科对此现象的解释(强调我的):

当JVM加载类Something时,该类将进行初始化。 由于该类没有任何静态变量来初始化,因此初始化完成很简单。 在JVM确定必须执行LazyHolder之前,不会初始化其中的静态类定义LazyHolder。 静态类LazyHolder仅在类Something上调用静态方法getInstance时执行,第一次发生这种情况时,JVM将加载并初始化LazyHolder类。 LazyHolder类的初始化导致静态变量通过执行外部类Something的(私有)构造函数来初始化。 由于类初始化阶段由JLS保证是串行的,即非并发的,因此在加载和初始化期间静态getInstance方法不需要进一步同步。 并且由于初始化阶段将静态变量写入串行操作中,因此getInstance的所有后续并发调用将返回相同的正确初始化内容,而不会产生任何额外的同步开销。

所以在你的例子中, Singleton是“LazyHolder”而SingletonHolder是“Something”。 由于JLS保证,两次调用getInstance()不会导致竞争条件。

我认为这个想法是Java保证一个类只能被初始化一次,所以隐式只有第一个访问的线程会导致这种情况发生

在调用Singleton.getInstance()时将创建Singleton,此时将实例化INSTANCE。 同步取决于conrete实现,因为没有要修改的值(在此示例中)不需要同步。

这里有两个不同之处需要注意。 第一个是Singleton设计模式,另一个是单例实例。

Singleton设计模式存在问题,因为设计模式表示使用静态实现的单例实例(由于各种原因这是一件坏事 – 特别是对于unit testing)。 第二个是一个实例,只有一个实例,无论如何(比如作为枚举实现的JVM单例)。

与Singleton模式相比,枚举版本绝对优越。

如果您不希望将所有实例都实现为enum实例,那么您应该考虑使用dependency injection( Google Guice等框架)来管理应用程序的单例实例。

这个成语被称为初始化按需持有者(IODH)习语,并且在Bob Java在他关于懒惰加载单身人士的伟大post中提醒的有效Java的第48项中进行了讨论:

第48项:同步对共享可变数据的访问

(……)

当初始化静态字段并且可能不需要时,初始化按需持有者类习惯用法适合使用,但如果需要则将集中使用。 这个成语如下所示:

 // The initialize-on-demand holder class idiom private static class FooHolder { static final Foo foo = new Foo(); } public static Foo getFoo() { return FooHolder.foo; } 

这个成语利用了在使用类之前不会初始化类的保证[ JLS ,12.4.1]。 当第一次调用getFoo方法时,它会读取字段FooHolder.foo ,导致FooHolder类被初始化。 这个习惯用法的getFoo在于getFoo方法不是同步的,只执行字段访问,因此延迟初始化几乎不会增加访问成本。 这个成语的唯一缺点是它不适用于实例字段,仅适用于静态字段。

但是,在Effective Java的第二版中,Joshua解释了如何使用枚举(ab)来编写可序列化的Singleton(这应该是Java 5的首选方法):

从版本1.5开始,有第三种方法来实现单例。 只需使用一个元素创建一个枚举类型:

 // Enum singleton - the preferred approach public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } } 

这种方法在function上等同于公共字段方法,除了它更简洁,免费提供序列化机制,并提供防止多个实例化的铁定保证,即使面对复杂的序列化或reflection攻击。 虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方法。