类的加载时机汇总

实践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() 方法返回。线程

Q:INSTANCE 在建立过程当中又是如何保证线程安全的呢?

《深刻理解JVM》引用:code

JVM 保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步。

若是多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>() 方法,
其余线程都须要阻塞等待,直到活动线程执行完 <clinit>() 方法。

当某个线程第一次执行了<clinit>() 这个初始化方法以后,其余线程就不会重复执行初始化了。

同一个加载器下,一个类只会被初始化一次。
复制代码

所以,一个静态内部类被加载初始化时,从类加载器的角度来看,是线程安全的,而单例对象 INSTANCE 在这个静态内部类中是一个静态 Field,因此它跟随这个静态内部类的加载而初始化,且只在类加载时初始化一次。对象

静态内部类初始化的缺陷

传参问题:ip

因为是静态内部类的形式去建立单例的,因此外部没法传递参数进去,如 Context 这种参数。get

所以,在建立单例时,能够在静态内部类与 DCL 模式之间权衡选择。

相关文章
相关标签/搜索