维基百科的单例模式实现

时间:2010-01-10 17:08:09

标签: java design-patterns singleton

我指的是维基百科上的solution for the Singleton Pattern by Bill Pugh

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不安全吗?如果是,那么它应该在代码中使用在哪里?

7 个答案:

答案 0 :(得分:12)

请参阅同一部分链接的文章“按需初始化持有人惯用语”中的"How it works"

简而言之,这是第一次致电getInstance()时会发生的事情:

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

答案 1 :(得分:5)

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

然而,since Java 5, the preferred approach involves Enum:

// Enum singleton - the preferred approach
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() { ... }
}

参考:Implementing the singleton pattern in Java

答案 2 :(得分:3)

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

  

当类Something被加载时   JVM,课程通过   初始化。既然上课了   没有任何静态变量   初始化,初始化   完成琐事。 静态类   其中的定义LazyHolder不是   初始化直到JVM确定   必须执行LazyHolder。   静态类LazyHolder只是   静态方法时执行   在类上调用getInstance   东西,这是第一次   发生JVM将加载和   初始化LazyHolder类。该   初始化LazyHolder类   导致静态变量的东西   通过执行来初始化   外部的(私有)构造函数   一些东西。 自上课以来   初始化阶段由保证   JLS是串行的,即   非并发,没有进一步   同步是必需的   静态getInstance方法   加载和初始化。从那以后   初始化阶段写入   静态变量串行中的东西   操作,所有后续并发   getInstance的调用将   返回相同的正确初始化   没有招致的东西   额外的同步开销。

因此,在您的示例中,Singleton是“LazyHolder”,SingletonHolder是“Something”。由于JLS保证,两次调用getInstance()不会导致竞争条件。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

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

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

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

答案 6 :(得分:0)

这个成语被称为Initialization on Demand Holder (IODH) idiom,并在Bob Lee在他关于Lazy Loading Singletons的精彩帖子中提醒的有效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方法未同步   并且只执行字段访问,所以   懒惰的初始化实际上增加了   没有任何访问成本。该   唯一的缺点是成语   它不适用于实例字段,   仅适用于静态字段。

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

  

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

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

除了它之外,这种方法在功能上等同于公共领域方法   更简洁,免费提供序列化机制,并提供   铁定的保证反对多重实例化,即使面对复杂   序列化或反射攻击。虽然这种方法尚未广泛应用   采用单元素枚举类型是实现单例的最佳方式。