文章目录

  • 一、代码
  • 二、静态内部类的优点:
  • 三、静态内部类又是如何实现线程安全
  • 3.1、 静态内部类就属于 被动使用
  • 3.2 、代码分析
  • 3.3、总结
  • 四、静态内部类的缺点


一、代码

public class SingleTon {         //外部类

	private SingleTon(){}	

	// 静态方法
	public static SingleTon getInstance(){
		return SingleTonHolder.INSTANCE ;
	}
	
	//静态内部类
	private static class SingleTonHolder {
	
		private static final  SingleTon  INSTANCE  = new SingleTon();
	}
}

二、静态内部类的优点:

外部类 加载时,并不需要立即加载 静态内部类静态内部类 不被加载则不去初始化 INSTANCE ,故而不占内存。
即当 SingleTon 第一次被加载时,并不需要去加载 SingleTonHolder

getInstance() 第一次被调用时,才会去初始化 INSTANCE,虚拟机加载 SingleTonHolder 类。

这种方法 不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。



三、静态内部类又是如何实现线程安全

3.1、 静态内部类就属于 被动使用

参考文章:
Java 类的主动使用 和 被动使用
JVM类加载的过程(类加载子系统)

静态内部类就属于 被动使用 ,所以不会进行初始化,即不会 对 静态变量 的初始化 和 静态代码块 的赋值。

3.2 、代码分析

Java单例未释放 java单例模式内部类_线程安全

getInstance() 被调用时,SingleTonHolder 才在 SingleTon运行时常量池 里,把 符号引用 替换为 直接引用 (即:类加载过程的 解析 阶段),这时, 静态对象 INSTANCE 也真正被创建 ,然后再被 getInstance() 返回出去,这点同饿汉模式。

那么,饿汉模式 是有线程安全问题的 ?

再回头看下 getInstance() ,调用的是 SingleTonHolder.INSTANCE ,跟上面那个DCL方法不同的是, getInstance() 方法并没有多次去 new 对象,故不管多少个线程去调用 getInstance() ,取的都是同一个 INSTANCE 对象,而不用去重新创建。

Java单例未释放 java单例模式内部类_内部类_02

那么 INSTANCE 在创建过程中又是如何保证线程安全的呢? 在《深入理解java虚拟机》中,这样描述(摘要):

1、 虚拟机会保证一个类的 <clinit>()多线程环境中被正确地加锁、同步
2、 如果多个线程同时去初始化一个类,那么,只会有一个线程去执行这个类的 <clinit>() ,其他线程都需要 阻塞等待
3、 需要注意的是第一个活动线程 执行 <clinit>() 后,其他线程被唤醒之后,不会再次进入 <clinit>() 。同一个类加载器下,一个类型只会初始化一次。在实际应用中,这种阻塞往往是很隐蔽的。

因此,可以看出 INSTANCE 在创建过程中是线程安全的,所以说 静态内部类 形式的单例 可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

3.3、总结

  1. 采用的类装载的机制保证初始化实例只有一个线程。
  2. 静态内部类在 SingleTon 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance(),才会被装载 SingleTonHolder 类,从而完成 SingleTon 的实例化。
  3. 类的静态属性(类)只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证线程的安全性,在类进行初始化时,别的线程是无法进入的。
  4. 优点:避免了线程不安全,利用静态内类特点实现延迟加载,效率高。

四、静态内部类的缺点

静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如Context这种参数,所以,我们创建单例时,可以在静态内部类与DCL模式(双重锁懒汉模式(Double Check Lock)) 里自己斟酌。