静态内部类懒加载实现原理

前言

最近在看《深入理解Java虚拟机》一书,看到了类初始化的五种情况。联想到之前看到过通过静态内部类来实现单例懒加载的模式,当时不理解其原理,现在特此记录一下。

由传统单例引起

传统的单例模式分为饿汉,懒汉模式。因为synchronized 关键字,导致性能被浪费。在实例化之后的对象读取,希望是无锁的。所以引入“双检锁模式”

public class Singleton{
    private static Singleton singleton;

    private Singleton() {
    }

    public synchronized static Single newInstance() {
        if (singleton== null) {
            singleton= new Singleton();
        }
        return singleton;
    }
}

双检锁

但是,这种双检锁模式也会存在问题,多线程情况下,会出现某个线程获取到的实例对象虽然被赋予默认值,但其初始化还未完成的情况。这种问题的解决办法则是添加volatile关键字,但volatile也会有性能损耗。

public class DoubleCheckedLock {

    private static DoubleCheckedLock instance;

    public static DoubleCheckedLock getInstance(){
        if (null == instance){
            synchronized (DoubleCheckedLock.class){
                if (null == instance){
                    instance = new DoubleCheckedLock();
                }
            }
        }
        return instance;
    }

}

静态内部类

这种方式通过jvm来保证其线程安全,而懒加载的机制则是依靠虚拟机规范制度的类“初始化”规则保证

public class Singleton {
    private static class SingletonHolder {
        private static Singleton singleton = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton newInstance() {
        return SingletonHolder.singleton;
    }
}

类初始化规则

虚拟机规范则严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要
在此之前开始):

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初
    始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字
    实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常
    量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,
    则需要先触发其初始化。
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父
    类的初始化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个
    类),虚拟机会先初始化这个主类。
  • 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后
    的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄
    所对应的类没有进行过初始化,则需要先触发其初始化。

参考:《深入理解Java虚拟机》

你可能感兴趣的:(Java)