这是我参与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()
方法完毕。同一个类加载器下,一种类型只会初始化一次。
那是不是用静态内部类就高枕无忧了呢?它也有一个比较隐秘的缺点,那就是由于是静态内部类的形式去创建单例的,故外部无法传递参数进去。这时唯有退而求其次,只能选择双重检查锁模式模式了。