实践Tip:经过单例模式引入JVM类加载机制的内容。java
首先,须要了解类的加载时机。安全
Java 虚拟机在有且仅有的 5 中场景下会对类进行初始化。bash
一、遇到 new、getstatic、setstatic 或 invokestatic 这 4 个字节码指令时,分别对应以下Java代码场景:
new : new一个实例化对象
getstatic 读取一个静态字段(final修饰、已在编译期把结果放入常量池的除外)
setstatic 设置一个静态字段(同上)
invokestatic 调用一个类的静态方法
二、使用 java.lang.reflect 包中的方法,对类进行反射调用时,若是类没有初始化过,会触发初始化之。
三、当初始化一个类时,若是父类未初始化,会先触发父类的初始化。
四、当虚拟机启动时,用户须要制定一个要执行的主类(含 main 方法的类),虚拟机会先初始化这个类。
五、当使用JDK 1.7等动态语言支持时,若是一个java.lang.invoke.MethodHandle
实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,
而且这个方法句柄所对应的类没有进行过初始化,则须要先触发其初始化。
复制代码
以上5中状况被称为类的主动引用。除此以外的全部对类的引用都不会对类进行初始化,被成为被动引用。多线程
静态内部类就是被动引用的类型。spa
当 getInstance() 方法被调用时,SingletonHolder 才在 Singleton 的运行时常量池里,把符号引用替换为直接引用,这时静态对象 INSTANCE 也才最终被建立,而后再被 getInstance() 方法返回。线程
《深刻理解JVM》引用:code
JVM 保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步。
若是多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>() 方法,
其余线程都须要阻塞等待,直到活动线程执行完 <clinit>() 方法。
当某个线程第一次执行了<clinit>() 这个初始化方法以后,其余线程就不会重复执行初始化了。
同一个加载器下,一个类只会被初始化一次。
复制代码
所以,一个静态内部类被加载初始化时,从类加载器的角度来看,是线程安全的,而单例对象 INSTANCE 在这个静态内部类中是一个静态 Field,因此它跟随这个静态内部类的加载而初始化,且只在类加载时初始化一次。对象
传参问题:ip
因为是静态内部类的形式去建立单例的,因此外部没法传递参数进去,如 Context 这种参数。get
所以,在建立单例时,能够在静态内部类与 DCL 模式之间权衡选择。