1.1 定义
单列(Singleton):保证一个类只有一个实例,并且提供一个全局访问点
1.2 应用场景
1.3实现方式
1.3.1 懒汉模式
延迟加载,只有在真正使用的时候才开始实例化。
public class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton() {
}
private static LazySingleton getInstance() {
if (instance == null) {//已经实例化后,就不需要进行加锁操作,避免影响性能
synchronized (LazySingleton.class) {
if (instance == null) {//多线程情况下,可能加锁瞬间已经有多个线程运行到此
instance = new LazySingleton();
//new LazySingleton在字节码层步骤:
//1.分配空间
//2.初始化
//3.引用赋值
// JIT,CPU有可能对2与3指令进行重排序,排序后如下
//1.分配空间
//3.引用赋值===>如果在当前指令执行完,有其他线程来获取实例,将拿到尚未初始化好的实例
//2.初始化
}
}
}
return instance;
}
}
注意点:
- 线程安全问题:使用synchronized
- 加锁优化:两次判断instance是否为null
- volatile关键字:编译器(JIT),CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰,对于volatile修饰的字段,可以防止指令重排。
1.3.2 饿汉模式
类加载的初始化阶段就完成了实例的初始化。本质就是借助JVM类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步形式完成类加载的整个过程)。
类加载过程:
- 加载二进制数据到内存中,生成对应的class数据结构。
- 连接:a.验证,b.准备(给类的静态成员变量赋默认值),c.解析
- 初始化:给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化如:
- 当前类是启动类即main函数所在类
- 直接进行new操作
- 访问静态属性、静态方法
- 用反射访问类
- 初始化一个类的子类
public class HungrySingleton {
private static HungrySingleton instance=new HungrySingleton();
public HungrySingleton() {
}
public static HungrySingleton getInstance(){
return instance;
}
}
1.3.3 静态内部类
public class InnerClassSingleton {
private static class InnerClassHolder {
private static InnerClassSingleton instance = new InnerClassSingleton();
public InnerClassHolder() {
}
}
public static InnerClassSingleton getInstance() {
return InnerClassHolder.instance;
}
}
- 本质上是利用类的加载机制来保证线程安全
- 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式
1.4 反射攻击与解决
反射攻击:
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
InnerClassSingleton instance = InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton == instance);
静态内部类防止反射攻破:
public class InnerClassSingleton {
private static class InnerClassHolder {
private static InnerClassSingleton instance = new InnerClassSingleton();
public InnerClassHolder() {
// 防反射攻击
if(InnerClassHolder.instance!=null){
throw new RuntimeException("单列不允许多个实例");
}
}
}
public static InnerClassSingleton getInstance() {
return InnerClassHolder.instance;
}
}
1.5 枚举类型
枚举类型也是单例模式,实现特点:
- 天然不支持反射创建对应的实例,且有自己的反序列化机制
利用类加载机制保证线程安全
public enum EnumSingleton {
INSTANCE;
public void print() {
System.out.println(this.hashCode());
}
}
1.6 序列化
可以利用指定方法替换反序列化流中的数据,如
ANY‐ACCESS‐MODIFIER Object readResolve() throws ObjectStreamException;
```java import java.io.ObjectStreamException; import java.io.Serializable;
public class InnerClassSingleton implements Serializable {
static final long serialVersionUID = 42L;//保证版本一致
private static class InnerClassHolder {
private static InnerClassSingleton instance = new InnerClassSingleton();
public InnerClassHolder() {
if (InnerClassHolder.instance != null) {
throw new RuntimeException("单列不允许多个实例");
}
}
}
public static InnerClassSingleton getInstance() {
return InnerClassHolder.instance;
}
Object readResolve() throws ObjectStreamException {
return InnerClassHolder.instance;
}
}
<a name="guAl4"></a>
# 1.7 源码中的应用
```java
//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