java设计模式之单例模式singleton

    科技2025-04-22  8

    模式定义

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

     

    单例模式的实现主要有以下四种

    一、懒汉模式

    public class LazySingleton { private static volatile LazySingleton instance; //不允许被实例化 private LazySingleton(){ } /** * 获取实例 * @return */ public static LazySingleton getInstance() { if(instance!=null){ synchronized (LazySingleton.class){ if(instance!=null) { instance = new LazySingleton(); /*一个new操作在字节码层面对应三步操作 * 1、开辟空间 * 2、初始化 * 3、变量赋值 * cpu可能会重排需2、3步。如果先执行了第3步,则会返回一个没有初始化的对象, * 发生空指针异常。增加volatile关键字,防止重排序 * */ } } } return instance; } }

    特点:延时加载,只有在正在需要的时候才开始实例化。

    1、线程安全问题

    2、双重判断double check,加锁优化(锁也可以直接加锁到方法上,但是会耗费性能)

    3、使用volatile关键字,防止指令重排序

     

    二、饿汉模式

    class HunglySingleton { private static HunglySingleton instance= new HunglySingleton(); //不允许被实例化 private HunglySingleton(){ } /** * 获取实例 * @return */ public static HunglySingleton getInstance() { return instance; } }

    特点:类加载的初始化阶段就完成了实例的初始化,本质上是借助于jvm的类加载机制,保证实例的唯一性。(急加载)

    类加载机制:

    1、加载:加载二进制文件到内存中,并生成对应的class数据结构

    2、连接:包括验证、准备(给静态变量赋默认值,引用类型为null,int为0,boolean为false等等)、解析(将符号应用替换为直接引用)

    3、初始化(执行静态代码块、给静态变量赋初值)

    注意:如果当前类的父类没有被加载,则会先加载父类

    三、静态内部类模式

    class InnerSingleton { private static class InnerSingletonHolder { private static InnerSingleton instance= new InnerSingleton(); } //不允许被实例化 private InnerSingleton(){ } /** * 获取实例 * @return */ public static InnerSingleton getInstance() { return InnerSingletonHolder.instance; } }

    特点:也是借助jvm的类加载机制,保证实例的唯一性(懒加载)

    当访问instance时才会初始化实例

    四、enum模式

    enum EnumSingleton { INSTANCE; /** * 获取实例 * @return */ public static EnumSingleton getInstance() { return INSTANCE; } }

    特点:简单,线程安全,也能够防护反射攻击(禁止反射实例化enum类型类)。

     

    特殊情况:

    一、当使用反射创建实例时还能保证单例吗?

    1、懒汉模式:无解

    2、饿汉模式

    解决方法:把构造方法增加判断,如果使用反射调用私有构造方法,则会抛异常。

    //不允许被实例化 private HunglySingleton(){ if(instance!=null){ throw new RuntimeException("单例不允许多例实例"); } }

    3、静态内部类模式

    同饿汉模式的解决办法

    4、enmu模式

    天然的,就不能被反射调用创建实例,当使用反射newInstance()就会抛异常。

    二、当使用序列化反序列化创建实例时还能保证单例吗?

    当我们对静态内部类模式测试一下,序列化并反序列化后,还是同一个实例吗?

    public class InnerSingletonTest { public static void main(String[] args) throws IOException, ClassNotFoundException { InnerSingleton instance = InnerSingleton.getInstance(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("innerSingletonObject")); objectOutputStream.writeObject(instance); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("innerSingletonObject")); InnerSingleton instance2 = (InnerSingleton)objectInputStream.readObject(); objectInputStream.close(); System.out.println(instance==instance2); } } class InnerSingleton implements Serializable { private static class InnerSingletonHolder { private static InnerSingleton instance= new InnerSingleton(); } //不允许被实例化 private InnerSingleton(){ if(InnerSingletonHolder.instance!=null){ throw new RuntimeException("单例不允许多例实例"); } } /** * 获取实例 * @return */ public static InnerSingleton getInstance() { return InnerSingletonHolder.instance; } }

     答案是false。因为反序列化没有调用InnerSingleton的构造方法。

    解决办法: 

    查看serializable接口的说明如下

     需要增加一个readResolve方法

    Object readResolve() throws ObjectStreamException{ return getInstance(); }

    注意:enum枚举类除外,有单独的处理方法,查看readObject()的源码如下:

     

    jdk和spring中单例模式的应用

     jdk:Runtime类(饿汉)、Currency

    spring:DefaultSingletonBeanRegistry、ReactiveAdapterRegistry、ProxyFactoryBean(aop包)

    Processed: 0.009, SQL: 8