设计模式 | 挑战单例模式(四)

324 阅读2分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

内容接着上篇文章《挑战单例模式(三)》,上篇文章讲到了双重检查锁单例实现方式,这篇文章来使用最广泛的静态内部类单例实现方式。

五、静态内部类

静态内部类的解决方案用到了JVM中类初始化的机制。它既保证延迟加载,避免内存浪费,又能兼顾性能同时保证线程安全,多线程环境下,只创建一个实例对象。

静态内部类实现方式如下:

public class SingletonInInnerClass {
    private SingletonInInnerClass(){}
​
    public static SingletonInInnerClass getInstance(){
        return Holder.INSTANCE;
    }
​
    private static class Holder{
        private static final SingletonInInnerClass INSTANCE = new SingletonInInnerClass();
    }
}

当加载SingletonInInnerClass单例类时,内部类不会被加载,因为在加载单例类的时候不会创建对象,只有再调用getInstance静态方法时才创建内部类,这个单例类的懒加载。目前是使用的最广泛的单例类创建形式了。

那么实现的原理是什么?简单地说,巧妙地用到了内部类一定是要在方法调用之前初始化。即外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故在需要用到实例化对象时,才创建对象。

JVM如何保证内部类只是被初始化一次?这个要从类的加载机制说起, JVM会保证一个类的clinit()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的clinit()方法,其他线程都需要阻塞等待,直到活动线程执行clinit()方法完毕。同一个类加载器下,一种类型只会初始化一次。

那是不是用静态内部类就高枕无忧了呢?它也有一个比较隐秘的缺点那就是由于是静态内部类的形式去创建单例的,故外部无法传递参数进去。这时唯有退而求其次,只能选择双重检查锁模式模式了。