模式定义:
保证一个类只有一个实例,并且提供一个全局访问点
场景:
重量级的对象,不需要多个实例,如线程池,数据库连接池。
1.懒汉模式
延迟加载, 只有在真正使用的时候,才开始实例化。
基本实现:
public class LazySingletonTest { public static void main(String[] args) { LazySingleton instance1 = LazySingleton.getInstance(); LazySingleton instance2 = LazySingleton.getInstance(); System.out.println(instance1==instance2); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){ } public static LazySingleton getInstance(){ if(instance==null){ instance = new LazySingleton(); } return instance; } }
单线程情况下没问题,但是多线程下就存在线程安全问题
public class LazySingletonTest { public static void main(String[] args) { new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){ } public static LazySingleton getInstance(){ if(instance==null){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } instance = new LazySingleton(); } return instance; } }
第一步优化,加锁并双重检查
public static LazySingleton getInstance(){ if(instance==null){ synchronized (LazySingleton.class){ if(instance==null){ instance = new LazySingleton(); } } } return instance; }
第二部优化,防治指令重排,造成并发情况下出现空指针,加入volatile关键字。
OK,保证线程安全的懒汉模式就是:
class LazySingleton{ private volatile static LazySingleton instance; private LazySingleton(){ } public static LazySingleton getInstance(){ if(instance==null){ synchronized (LazySingleton.class){ if(instance==null){ instance = new LazySingleton(); } } } return instance; } }
2.饿汉模式
类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。
实现:
class HungrySingleton{ private static HungrySingleton instance = new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance(){ return instance; } }
3.静态内部类
- 本质上是利用类的加载机制来保证线程安全
- 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。
class InnerClazzSingleton{ private static class InnerClassHolder{ private static InnerClazzSingleton instance = new InnerClazzSingleton(); } private InnerClazzSingleton(){ } public static InnerClazzSingleton getInstance(){ return InnerClassHolder.instance; } }
4.反射攻击
对于上述的懒汉、恶汉、静态内部类的单例模式,均可以通过反射破解,以静态内部类为例:
public class InnerClazzSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<InnerClazzSingleton> constructor = InnerClazzSingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); InnerClazzSingleton innerClazzSingleton = constructor.newInstance(); InnerClazzSingleton instance = InnerClazzSingleton.getInstance(); System.out.println(innerClazzSingleton==instance); } } class InnerClazzSingleton{ private static class InnerClassHolder{ private static InnerClazzSingleton instance = new InnerClazzSingleton(); } private InnerClazzSingleton(){ } public static InnerClazzSingleton getInstance(){ return InnerClassHolder.instance; } }
- 对于恶汉模式和静态内部类可以用以下方式防止反射破解,但是懒汉模式不能防止反射破解
class InnerClazzSingleton{ private static class InnerClassHolder{ private static InnerClazzSingleton instance = new InnerClazzSingleton(); } private InnerClazzSingleton(){ if(InnerClassHolder.instance!=null){ throw new RuntimeException("单例不允许多个实例"); } } public static InnerClazzSingleton getInstance(){ return InnerClassHolder.instance; } }
5.枚举类型
天然不支持反射创建对应的实例,且有自己的反序列化机制。
利用类加载机制保证线程安全。
public enum EnumSingleton { INSTANCE; } class EnumSingletonTest{ public static void main(String[] args) { EnumSingleton instance = EnumSingleton.INSTANCE; EnumSingleton instance1 = EnumSingleton.INSTANCE; System.out.println(instance==instance1); } }
6.序列化
可以利用 指定方法来替换从反序列化流中的数据,枚举类型天然支持反序列化
public class InnerClazzSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { InnerClazzSingleton instance = InnerClazzSingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerClazzSingleton")); oos.writeObject(instance); oos.close(); /*ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerClazzSingleton")); InnerClazzSingleton innerClazzSingleton = (InnerClazzSingleton) ois.readObject(); System.out.println(innerClazzSingleton==instance);*/ } } class InnerClazzSingleton implements Serializable { //static final long serialVersionUID = 42L; private static class InnerClassHolder{ private static InnerClazzSingleton instance = new InnerClazzSingleton(); } private InnerClazzSingleton(){ if(InnerClassHolder.instance!=null){ throw new RuntimeException("单例不允许多个实例"); } } public static InnerClazzSingleton getInstance(){ return InnerClassHolder.instance; } /*Object readResolve() throws ObjectStreamException { return InnerClassHolder.instance; }*/ }
序列化后输出到文件里
反序列化后可以看到和我们通过getInstance()拿到的对象不一致,单例变成了多例
加入版本号和readResolve防范
static final long serialVersionUID = 42L;
Object readResolve() throws ObjectStreamException { return InnerClassHolder.instance; }
7.源码中的应用
// Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry