相信对于单例模式大家都非常熟悉,用来保证一个类的对象只能被实例化一次。常见的实现方法有懒汉式,饿汉式。先分析这两种实现方式的弊端再来介绍今天要介绍的静态内部类实现方式
饿汉式
pulbic class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getInstance(){return instance;}
}
实现方式非常简单,私有化构造方法,定义一个私有静态变量随机类加载而直接实例化,对外提供一个访问接口保证每次都访问同一个实例。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
懒汉式–双重检验锁+volatile实现
这里直接介绍线程安全的实现方式
public class DoubleCheckVolatile {
private static volatile DoubleCheckVolatile instance = null;
private DoubleCheckVolatile() {
super();
}
public static DoubleCheckVolatile getInstance() {
if(instance != null)
return instance;
synchronized (DoubleCheckVolatile.class) {
if (instance == null) {
instance = new DoubleCheckVolatile();
// 1 给instance分配空间
// 2 初始化instance
// 3 返回对象的地址给引用
//由于happend-before的存在,线程A中执行顺序可能变成
//1 3 2,直接返回了引用
//那么线程b得到的对象并没有初始化完成,造成错误,所以要加volatile防止指令重排序
}
return instance;
}
}
}
虽然这样方法可以在 保证线程安全 和延迟加载 的前提下实现单例,但是可见代码非常繁琐,如果没有考虑到指令重排可能造成安全问题
有没一种既能保证线程安全,又可以实现延迟加载,还容易理解的单例实现方式呢?
下面开始介绍标题中所提到的,静态内部类实现
public class Singleton {
private Singleton(){
}
private static class Inner{
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Inner.instance;
}
}
在这种实现方式中,在getInstance()中会返回Inner的instance变量,根据类加载的特点。
Single的静态内部类只有在被调用成员变量时才会被加载
所以对应获取单例对象的过程是:第一个线程调用getInstance()方法,Inner类开始加载,并初始化了实例对象instance,返回引用,同时JVM会保证类加载的线程安全。随后的线程调用getInstance()方法,由于static变量instance只会随类加载实例化一次,所以每次得到的都是同一个对象
<<深入理解Java虚拟机>>中对于类加载的5个触发条件描述
什么情况下需要开始类加载过程的第一个阶段:加载?Java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
- 1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
- 2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。