使用volatile 修饰Singleton ,保证INSTANCE变量的可见性和有序性。
不使用volatile会导致多线程环境下,由于synchronized代码块内的指令重排,导致线程获得未初始化完的单例。见下图。
public final class Singleton { private Singleton() { } private static volatile Singleton INSTANCE = null; public static Singleton getInstance() { // 实例没创建,才会进入内部的 synchronized代码块 if (INSTANCE == null) { synchronized (Singleton.class) { // t2 // 也许有其它线程已经创建实例,所以再判断一次 if (INSTANCE == null) { // t1 INSTANCE = new Singleton(); } } } return INSTANCE; } }
以上的实现特点是:
● 懒惰实例化
● 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁
● 有隐含的,但很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外。 使用volatile 修饰Singleton ,保证INSTANCE变量的可见性和有序性。
字节码角度分析方法内代码:
// -------------------------------------> 加入对 INSTANCE 变量的读屏障 0: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; 3: ifnonnull 37 6: ldc #3 // class cn/itcast/n5/Singleton 8: dup 9: astore_0 10: monitorenter -----------------------> 保证原子性、可见性 11: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; 14: ifnonnull 27 17: new #3 // class cn/itcast/n5/Singleton 20: dup 21: invokespecial #4 // Method "<init>":()V 24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; // -------------------------------------> 加入对 INSTANCE 变量的写屏障 27: aload_0 28: monitorexit ------------------------> 保证原子性、可见性 29: goto 37 32: astore_1 33: aload_0 34: monitorexit 35: aload_1 36: athrow 37: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; 40: areturn
执行流程如下
更底层是读写变量时使用 lock 指令来多核 CPU 之间的可见性与有序性。