最高效的单例模式实现-静态内部类方式

相信对于单例模式大家都非常熟悉,用来保证一个类的对象只能被实例化一次。常见的实现方法有懒汉式,饿汉式。先分析这两种实现方式的弊端再来介绍今天要介绍的静态内部类实现方式

饿汉式

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的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值