Initialization On Demand Holder idiom的实现探讨

SingletonLogoTransparent

起源

程晓明同学的文章“双重检查锁定与延迟初始化”中,提到了对于单例模式的“Initialization On Demand Holder idiom”实现方案。

这个方案的技术实质是利用Java类初始化的LC锁。相比其他实现方案(如double-checked locking等),该技术方案的实现代码较为简洁,并且在所有版本的编译器中都是可行的。该方案代码如下:

[code lang=”Java”]
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}

public static Instance getInstance() {
return InstanceHolder.instance ; //这里将导致InstanceHolder类被初始化
}
}
[/code]

根据wikipedia中的解释,“双重检查锁定与延迟初始化”这篇文章中的实现,实际上是Steve Quirk早期的实现,Bill Pugh在其基础上将LazyHolder.INSTANCE的修饰符修改为private static final,实现代码如下:

[code lang=”Java”]
public class Something {
private Something() {}

private static class LazyHolder {
private static final Something INSTANCE = new Something();
}

public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
[/code]

然而,在另外一篇wikipedia的说明中,LazyHolder.INSTANCE的修饰符则为public static final

[code lang=”Java”]
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 {
public static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
[/code]

那么上述三种实现,究竟哪一种实现较为合理?

在讨论这个问题前,首先我们需要明确的是,上述三种方案都是可以保证线程安全的。
JLS12.4中关于一个类或接口将被立即初始化的相关说明:

  1. T is a class and an instance of T is created.
  2. T is a class and a static method declared by T is invoked.
  3. A static field declared by T is assigned.
  4. A static field declared by T is used and the field is not a constant variable (§4.12.4).
  5. T is a top level class (§7.6), and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.

显然,上述任意一种实现首次执行Singleton.getInstance()时,由于使用InstanceHolder.instance变量,将会导致InstanceHolder初始化,符合第四条。注意final关键字修饰的变量,并不一定是constant variable。引用JLS4.12.4中的相关说明:

A variable of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28), is called a constant variable.

只有基本类型或者是String类型的变量,才可能成为constant variable。
回到刚才的问题,在stackoverflow上有相关问题的回答。

  1. 首先,关于final关键字:
    http://stackoverflow.com/questions/20995036/is-initialization-on-demand-holder-idiom-thread-safe-without-a-final-modifier
    根据该问题的两个回答,不论有无final关键字,事实上都可以保证线程安全,final关键的作用仅在于防止对INSTANCE做修改。
  2. 然后是关于INSTANCE的访问权限问题:
    http://stackoverflow.com/questions/21604243/correct-implementation-of-initialization-on-demand-holder-idiom
    这里提出的观点是建议如下形式的实现:
    [code lang=”Java”]
    public class Singleton {

    private static class LazyHolder {
    static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
    return LazyHolder.INSTANCE;
    }
    }
    [/code]

    即并不在INSTANCE变量前添加public或者private修饰,即使用默认的package private访问权限。
    不用public的原因是这个修饰不够合理,作者认为事实上在LazyHolder是被限定为private的情况下,使用public来修饰INSTANCE并没有问题,然而如果有人修改LazyHolder为public,那么就可能出现问题。
    不用private的原因是,Java编译器中会保证类内部的private字段无法被外部访问,然而JLS6.6.1中有相应的规定:

Otherwise, if the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

所以外部类拥有对内部类中private字段的访问权限,那么在这个情况下,编译器就会有一些小技巧来保证外部类对内部类private字段的访问权限,即在内部类中插入packge private method,使得外部类调用这些getter和setter方法的形式来访问内部类的private字段。

综上所述,从线程安全的角度而言,上述的几种“Initialization On Demand Holder idiom”实现都是没有问题的。如果讨论细节上的合理性,那么更推荐最后一种实现,即:

[code lang=”Java”]
public class Singleton {

private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
[/code]

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Initialization On Demand Holder idiom的实现探讨

  • Trackback 关闭
  • 评论 (4)
    • heipacker
    • 2014/02/21 1:12下午

    不知道说的是啥。。。。。

  1. Really appreciate you sharing this blog.Much thanks again. Really Cool.

  2. Java8 中的StyleManager 就用了最后推荐的方式

    • linling
    • 2017/08/13 4:20下午

    看书遇到些疑惑,在文中找打答案了。 感谢?

return top