单例设计模式

Singleton

知识点

1.模式定义/应用场景/类图分析
2.字节码知识/字节码指令重排序
3.类加载机制
4.JVM序列化机制
5.单例模式在Spring框架 & JDK源码中的应用

模式定义

保证一个类只有一个实例,并且提供一个全局访问点

场景:

重量级的对象,不需要多个实例,如线程池,数据库连接池。

classDiagram

class Singleton {
 - singleton : Singleton
 - Singleton()
 + getInstance(): Singleton
}

1.懒汉模式

延迟加载, 只有在真正使用的时候,才开始实例化。
1)线程安全问题
2)double check 加锁优化
3)编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化
的实例,可以通过添加volatile 关键字进行修饰,
对于volatile 修饰的字段,可以防止指令重排。

2.饿汉模式

类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm
类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安
全(JVM以同步的形式来完成类加载的整个过程)。
类加载过程:
1,加载二进制数据到内存中, 生成对应的Class数据结构,
2,连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
3,初始化: 给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化 如( 当前类是启动类即
main函数所在类,直接进行new 操作,访问静态属性、访问静态方
法,用反射访问类,初始化一个类的子类等.)

3.静态内部类

1).本质上是利用类的加载机制来保证线程安全
2).只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一
种形式。

4.反射攻击实例

1
2
3
4
5
Constructor<InnerClassSingleton> declaredConstructor=InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible( true );
InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance();
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton==instance);

静态内部类防止反射破坏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class InnerClassSingleton {


private static class InnerClassHolder{
private static InnerClassSingleton instance= new InnerClassSingleton();
}

private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException( " 单例不允许多个实例 " );
}

}

public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}

5.枚举类型

1)天然不支持反射创建对应的实例,且有自己的反序列化机制
2)利用类加载机制保证线程安全

1
2
3
4
5
6
7
public enum EnumSingleton {
INSTANCE;

public void print(){
System.out.println(this.hashCode());
}
}

6.序列化

1)可以利用 指定方法来替换从反序列化流中的数据 如下

源码中的应用

1
2
3
4
5
6
7
8
9
// Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry
// Tomcat
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
// 反序列化指定数据源
java.util.Currency