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 & JDKjava.lang.Runtimeorg.springframework.aop.framework.ProxyFactoryBeanorg.springframework.beans.factory.support.DefaultSingletonBeanRegistryorg.springframework.core.ReactiveAdapterRegistry// Tomcatorg.apache.catalina.webresources.TomcatURLStreamHandlerFactory//反序列化指定数据源java.util.Currency
