[译]Initialization on demand holder单例模式实现
wiki:https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
Initialization on demand holder(设计模式)是一种延迟加载的单例模式,在Java的所有版本中,这个方法很好的实现了安全、高并发的延迟初始化。
1 2 3 4 5 6 7 8 9 10 11 |
public class Something { private Something() {} private static class LazyHolder { private static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } } |
这种方法的实现依赖于JVM(Java虚拟机)对类加载过程中初始化(initialization)阶段的执行。
当Something类被JVM加载,这个类便会经历初始化过程。这个类没有其他的静态变量,初始化过程会顺利完成,而内部静态类并不会被初始化,直到JVM确定LazyHolder会被执行才会去初始化。
当Something的静态方法 getInstance() 被调用时,LazyHolder才会被执行。当这件事第一次发生时,JVM就会去加载并初始化LazyHolder类。而LazyHolder的初始化导致了静态变量 INSTANCE 会被外部类(Something)的私有构造器初始化。
JLS(Java语言规范)中保证了类的初始化阶段是连续的。
虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕。[1]
(注: <clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。)
因此所有后来的并发调用 getInstance() 方法都会返回同一个被正确初始化的 INSTANCE 而不会有额外的同步开销。
这种方法给出了一个高效、线程安全的单例缓存而没有额外的同步开销。它甚至远快于非竞争同步。
尽管这种实现非常简洁,但是任何初始化失败都会导致单例类不可用。这意味着这种模式只能用于能保证初始化不会失败的情况下。
下面给出了初始化失败的情景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class PughFail { public static class Something { private Something() { super(); System.out.println(this.getClass().getName() + " called"); if (System.currentTimeMillis() > 0) { System.out.println("EMULATING INIT FAILURE"); throw new RuntimeException("EMULATING INIT FAILURE"); } } private static class LazyHolder { private static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } } public static void main(String[] args) { System.out.println("First try"); try { Something.getInstance(); } catch (Throwable t) { System.out.println(t); } System.out.println("Second try"); try { Something.getInstance(); } catch (Throwable t) { System.out.println(t); } } } |
运行输出:
1 2 3 4 5 6 |
First try PughFail$Something called EMULATING INIT FAILURE java.lang.ExceptionInInitializerError Second try java.lang.NoClassDefFoundError: Could not initialize class PughFail$Something$LazyHolder |
当第一次访问时初始化失败,第二次访问便会直接抛出 NoClassDefFoundError 异常
引用
- 《深入理解Java虚拟机》 作者:周志明,机械工业出版社
Categories: 设计模式