文章目录
- 一、代码
- 二、静态内部类的优点:
- 三、静态内部类又是如何实现线程安全
- 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 、代码分析
当 getInstance()
被调用时,SingleTonHolder
才在 SingleTon
的 运行时常量池
里,把 符号引用
替换为 直接引用
(即:类加载过程的 解析
阶段),这时, 静态对象 INSTANCE
也真正被创建 ,然后再被 getInstance()
返回出去,这点同饿汉模式。
那么,饿汉模式 是有线程安全问题的 ?
再回头看下 getInstance()
,调用的是 SingleTonHolder.INSTANCE
,跟上面那个DCL方法不同的是, getInstance()
方法并没有多次去 new 对象,故不管多少个线程去调用 getInstance()
,取的都是同一个 INSTANCE
对象,而不用去重新创建。
那么 INSTANCE
在创建过程中又是如何保证线程安全的呢? 在《深入理解java虚拟机》中,这样描述(摘要):
1、 虚拟机会保证一个类的
<clinit>()
在 多线程环境中被正确地加锁、同步 。
2、 如果多个线程同时去初始化一个类,那么,只会有一个线程去执行这个类的<clinit>()
,其他线程都需要 阻塞等待。
3、 需要注意的是,第一个活动线程 执行<clinit>()
后,其他线程被唤醒之后,不会再次进入<clinit>()
。同一个类加载器下,一个类型只会初始化一次。在实际应用中,这种阻塞往往是很隐蔽的。
因此,可以看出 INSTANCE
在创建过程中是线程安全的,所以说 静态内部类 形式的单例 可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
3.3、总结
- 采用的类装载的机制保证初始化实例只有一个线程。
- 静态内部类在
SingleTon
类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance()
,才会被装载SingleTonHolder
类,从而完成SingleTon
的实例化。 - 类的静态属性(类)只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,利用静态内类特点实现延迟加载,效率高。
四、静态内部类的缺点
静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如Context这种参数,所以,我们创建单例时,可以在静态内部类与DCL模式(双重锁懒汉模式(Double Check Lock)) 里自己斟酌。