一、单例模式的定义及使用场景
模式定义:保证一个类只有一个实例,并且提供一个全局访问点。
场景:重量级的对象,不需要多个实例,如线程池,数据库连接池。
二、单例模式的实现方式
1.懒汉模式:延迟加载,只有在真正使用的时候,才开始实例化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class LazySingleton{
private volatile static LazySingleton lazySingleton; private LazySingleton() {}
public static LazySingleton getInstance(){ if (null == lazySingleton){ synchronized (LazySingleton.class){ if (null == lazySingleton){ lazySingleton = new LazySingleton(); } } } return lazySingleton; } }
|
1)线程安全问题
2)double check,枷锁优化
3)编译器(JIT),CPU有可能对指令进行重排序,导致使用到未初始化的实例,可以通过添加volatile关键字进行修饰,对于volatile修饰的字段,可以防止指令重拍。
2.饿汉模式:类加载的初始化阶段就完成了实例的初始化,本质上就是借助于jvm类加载机制,保证实例的唯一性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){ if (HungrySingleton.getInstance()!=null){ throw new RuntimeException("单例模式不允许多个实例存在!!!"); } }
public static HungrySingleton getInstance(){ return hungrySingleton; } }
|
类加载过程:
1)加载二进制数据到内存中,生成对应的Class数据结构
2)连接:a.验证,b.准备(给类的静态成员变量赋默认值),c.解析
3)初始化:给类的静态变量赋值
只有在真正使用对应的类时,才会触发初始化(当前类时启动类即main函数所在类,直接new操作,访问静态属性,访问静态方法,用反射访问类,初始化一个类的子类等)。
3.静态内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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; } }
|
1)本质上是利用类的加载机制来保证线程安全
2)只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。
4.枚举类型
1 2 3 4 5 6 7 8
| public enum EnumSingleton {
INSTANCE();
public void print(){ System.out.println(INSTANCE.hashCode()); } }
|
1)天然不支持反射创建的实例,且有自己的反序列化机制
2)利用类加载自己保证线程安全
5.反射攻击实例
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);
|
6.序列化
对象需要实现
1 2 3 4 5 6 7 8
| class InnerClassSingleton implements Serializable {
private static final long serialVersionUID = 1324250553112887638L;
Object readResolve() throws ObjectStreamException{ return InnerClassHolder.instance; } }
|
1)可以利用指定方法来替换从反序列化流中的数据 如下:
1
| ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
|
Spring源码应用
1 2 3 4 5 6 7 8 9
| java.lang.Runtime org.springframework.aop.framework.ProxyFactoryBean org.springframework.beans.factory.support.DefaultSingletonBeanRegistry org.springframework.core.ReactiveAdapterRegistry
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
java.util.Currency
|