单例设计模式详解

单例模式(创建型模式)

1. 单例模式介绍

一个类在全局仅创建一个实例,允许在多线程访问时仅返回同一个实例

2. 单例模式原则

  • 私有构造
  • 使用静态方法或枚举
  • 考虑多线程情况
  • 考虑反射、序列化、克隆的情况

3. 场景案例

  • Spring 默认单例 bean 的使用
  • 数据库的连接池使用单例模式
  • 业务中设置全局的属性保存:项目中使用 Jedis 的单例来连接访问 Redis

4. 好处

  • 减少频繁创建及销毁的资源消耗
  • 减少 new 次数,也可减少 GC 操作

5. 七种源码

1. 静态类使用(线程安全、非懒加载)

public class Singleton_00 {

    /**
     * 我们业务开发经常使用,没有懒加载,在第一次运行的时候就直接初始化 cache
     */
    public static Map<String, Object> cache = new ConcurrentHashMap<>();
}

2. 懒汉模式(线程安全、懒加载)

  • 锁定整个方法效率低下,不建议使用
public class Singleton_01 implements Cloneable,Serializable {

    private static final long serialVersionUID = -6240753600251410378L;

    // 不能直接调用静态变量,因为使用的是懒加载
    private static Singleton_01 instance;

    /**
     * 私有构造器,不允许外部创建
     * 解决反射破坏单例模式
     */
    private static int count = 0;
    private Singleton_01() {
        synchronized (Singleton_01.class) {
            if (count > 0) {
                throw new RuntimeException("单例对象被反射破坏");
            }
            count++;
        }
    }

    /**
     * 添加 synchronized 锁整个方法,
     * 仅在第一次、第一个线程调用此方法时才创建对象
     */
    public static synchronized Singleton_01 getInstance() {
        if (null != instance) {
            return instance;
        }
        instance = new Singleton_01();
        return instance;
    }

    @Override
    public Singleton_01 clone() throws CloneNotSupportedException {
//        return (Singleton_01) super.clone();
        // 解决克隆破坏单例模式
        return instance;
    }

    /**
     * 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    public Object readResolve() {
        return instance;
    }
}

3. 饿汉模式(线程安全、非懒加载)

  • 不进行懒加载,大文件可能导致内存占用过大
public class Singleton_02 implements Cloneable,Serializable {

    private static final long serialVersionUID = -1873983845481394404L;
    /**
     * 私有化成员变量、仅能通过方法获取
     * 不进行懒加载,在 JVM 创建类的时候立即创建对象
     */
    private static Singleton_02 instance = new Singleton_02();

    /**
     * 私有化构造器,不允许外部构造
     */
    private Singleton_02 (){
    }

    public static Singleton_02 getInstance() {
        return instance;
    }

    @Override
    public Singleton_02 clone() throws CloneNotSupportedException {
        return (Singleton_02) super.clone();
    }
}

4. 双重锁校验-DCL(线程安全、懒加载)

public class Singleton_03 implements Cloneable,Serializable {

    private static final long serialVersionUID = -7783438578663397638L;
    /**
     * 懒汉模式而使用懒加载
     * volatile 防止实例化时,指令重排导致多线程异常
     * (因为 new 非原子操作,但是 volatile 不是将其变成原子操作,而是禁止指令重排,
     * 因为 new 主要分为三步,1-分配空间、2-初始化、3-引用指向对象,如果某个线程指令重排为 132,则另一个线程可能返回 null)
     */
    private static volatile Singleton_03 instance;

    /**
     * 私有化构造器,不允许外部构造
     */
    private Singleton_03() {
    }

    /**
     * 在操作内部加锁,效率更高
     * 但可读性较差,使用频率较低
     */
    public static Singleton_03 getInstance() {
        if (null != instance) {
            return instance;
        }
        synchronized (Singleton_03.class) {
            if (null == instance) {
                instance = new Singleton_03();
            }
        }
        return instance;
    }

    @Override
    public Singleton_03 clone() throws CloneNotSupportedException {
        return (Singleton_03) super.clone();
    }
}

5. 静态内部类(线程安全、懒加载)

public class Singleton_04 implements Cloneable,Serializable {

    private static final long serialVersionUID = 5036852687082314868L;

    /**
     * 即保证了线程安全,有保证了懒加载,还不需要加锁
     * 原因是使用了 JVM 加载类时只允许一个线程处理的机制,并且 JVM 进行类加载也是懒加载方式(使用的时候才加载)
     */
    private static class Singleton_04_inner {
        private static Singleton_04 instance = new Singleton_04();
    }

    private Singleton_04() {
    }

    public static Singleton_04 getInstance() {
        return Singleton_04_inner.instance;
    }

    @Override
    public Singleton_04 clone() throws CloneNotSupportedException {
        return (Singleton_04) super.clone();
    }

}

6. 枚举(线程安全,非懒加载,可防止反射/克隆/序列化破坏单例)

public enum Singleton_05 implements Cloneable {

    /**
     * 反编译可以看出:它只是语法糖,本质上继承了 Enum 类,然后将 INSTANCE 放在 static 代码块中 new
     * public final class singleton.Singleton_05 extends java.lang.Enum<singleton.Singleton_05> {
     *   public static final singleton.Singleton_05 INSTANCE;
     *   static {};
     *     Code:
     *        0: new           #4                  // class singleton/Singleton_05
     *        3: dup
     *        4: ldc           #7                  // String INSTANCE
     */
    INSTANCE;

    Singleton_05(){
    }
}

7. CAS AtomicReference(线程安全、懒加载)

public class Singleton_06 implements Cloneable,Serializable {

    private static final long serialVersionUID = 1729508621571327122L;

    private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<>();

    private Singleton_06() {
    }

    /**
     * 调用时才创建类的懒加载
     * 使用 JUC 包的 CAS 的忙等操作,无锁化只允许一个线程去实例化
     * 但是 CAS 也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中
     */
    public static Singleton_06 getInstance() {
        while (true) {
            Singleton_06 instance = INSTANCE.get();
            if (null != instance) {
                return instance;
            }
            INSTANCE.compareAndSet(null, new Singleton_06());
        }
    }

    @Override
    public Singleton_06 clone() throws CloneNotSupportedException {
        return (Singleton_06) super.clone();
    }
}

6. 问题与解决方案

1. 反射破坏单例

  • 上述方法中(除第一种)仅有使用 Enum 枚举的第六种方法不会被破坏,其他单例模式均会被破坏
  • 测试方法代码
    @Test
    public void testReflex() {
        try {
            System.out.println("testReflex");
            /**
             * 都能被反射破坏单例
             * 解决方法:在私有构造器中判断是否存在对象,存在则直接抛出异常
             */
            reflex01();
            reflex02();
            reflex03();
            reflex04();
            reflex06();
            // 枚举类反射中有判断,会抛出异常:不支持创建枚举
            reflex05();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void reflex01() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            InstantiationException {
        Singleton_01 singleton01 = Singleton_01.getInstance();

        Constructor<Singleton_01> construtor = Singleton_01.class.getDeclaredConstructor();
        construtor.setAccessible(true);
        Singleton_01 singleton01Temp = construtor.newInstance();

        System.out.println("Singleton_01");
        System.out.println(singleton01);
        System.out.println(singleton01Temp);
    }

    private void reflex02() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            InstantiationException {
        Singleton_02 singleton02 = Singleton_02.getInstance();

        Constructor<Singleton_02> construtor = Singleton_02.class.getDeclaredConstructor();
        construtor.setAccessible(true);
        Singleton_02 singleton02Temp = construtor.newInstance();

        System.out.println("Singleton_02");
        System.out.println(singleton02);
        System.out.println(singleton02Temp);
    }

    private void reflex03() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            InstantiationException {
        Singleton_03 singleton03 = Singleton_03.getInstance();

        Constructor<Singleton_03> construtor = Singleton_03.class.getDeclaredConstructor();
        construtor.setAccessible(true);
        Singleton_03 singleton03Temp = construtor.newInstance();

        System.out.println("Singleton_03");
        System.out.println(singleton03);
        System.out.println(singleton03Temp);
    }

    private void reflex04() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            InstantiationException {
        Singleton_04 singleton04 = Singleton_04.getInstance();

        Constructor<Singleton_04> construtor = Singleton_04.class.getDeclaredConstructor();
        construtor.setAccessible(true);
        Singleton_04 singleton04Temp = construtor.newInstance();

        System.out.println("Singleton_04");
        System.out.println(singleton04);
        System.out.println(singleton04Temp);
    }

    /**
     * 枚举类反射中有判断,会抛出异常:不支持创建枚举
     */
    private void reflex05() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            InstantiationException, ClassNotFoundException {
        Singleton_05 singleton05 = Singleton_05.INSTANCE;

        // 枚举本质上继承 java.lang.Enum 枚举类,而枚举比较特殊不能用 super 帮助构造,因此需添加 name 与 ordinary 俩枚举类自带的参数
        Constructor<Singleton_05> construtor = Singleton_05.class.getDeclaredConstructor(String.class, int.class);
        construtor.setAccessible(true);
        // java.lang.IllegalArgumentException: Cannot reflectively create enum objects
        Singleton_05 singleton05Temp = (Singleton_05) construtor.newInstance();

        System.out.println("Singleton_05");
        System.out.println(singleton05);
        System.out.println(singleton05Temp);
    }

    private void reflex06() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            InstantiationException {
        Singleton_06 singleton06 = Singleton_06.getInstance();

        Constructor<Singleton_06> construtor = Singleton_06.class.getDeclaredConstructor();
        construtor.setAccessible(true);
        Singleton_06 singleton06Temp = (Singleton_06) construtor.newInstance();

        System.out.println("Singleton_06");
        System.out.println(singleton06);
        System.out.println(singleton06Temp);
    }

  • 解决方法:在私有构造器中判断是否存在对象,存在则直接抛出异常
  • 解决方案代码
    /**
     * 私有构造器,不允许外部创建
     * 解决反射破坏单例模式
     */
    private static int count = 0;
    private Singleton_01() {
        synchronized (Singleton_01.class) {
            if (count > 0) {
                throw new RuntimeException("单例对象被反射破坏");
            }
            count++;
        }
    }

2. 克隆破坏单例

  • 上述方法中(除第一种)仅有使用 Enum 枚举的第六种方法不会被破坏,其他单例模式均会被破坏
  • 测试方法代码
    @Test
    public void testClone() {
        try {
            System.out.println("testClone");
            /**
             * 都会被克隆破坏,但是注意类必须实现 Cloneable 接口并重写 clone() 方法,才可以被调用
             * 解决方法:重写 clone() 方法,返回已经当前对象
             */
            clone01();
            clone02();
            clone03();
            clone04();
            clone06();
            // 枚举类中自带 final clone() 方法,无法重写,调用也会抛出异常:不支持克隆
            clone05();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

    private void clone01() throws CloneNotSupportedException {
        Singleton_01 singleton01 = Singleton_01.getInstance();

        Singleton_01 singleton01Temp = singleton01.clone();

        System.out.println("Singleton_01");
        System.out.println(singleton01);
        System.out.println(singleton01Temp);
    }

    private void clone02() throws CloneNotSupportedException {
        Singleton_02 singleton02 = Singleton_02.getInstance();

        Singleton_02 singleton02Temp = singleton02.clone();

        System.out.println("Singleton_02");
        System.out.println(singleton02);
        System.out.println(singleton02Temp);
    }

    private void clone03() throws CloneNotSupportedException {
        Singleton_03 singleton03 = Singleton_03.getInstance();

        Singleton_03 singleton03Temp = singleton03.clone();

        System.out.println("Singleton_03");
        System.out.println(singleton03);
        System.out.println(singleton03Temp);
    }

    private void clone04() throws CloneNotSupportedException {
        Singleton_04 singleton04 = Singleton_04.getInstance();

        Singleton_04 singleton04Temp = singleton04.clone();

        System.out.println("Singleton_04");
        System.out.println(singleton04);
        System.out.println(singleton04Temp);
    }

    /**
     * 枚举类中自带 final clone() 方法,无法重写,调用也会抛出异常:不支持克隆
     */
    private void clone05() throws CloneNotSupportedException {
        Singleton_05 singleton05 = Singleton_05.INSTANCE;

        Singleton_05 singleton05Temp = null;

        System.out.println("Singleton_05");
        System.out.println(singleton05);
        System.out.println(singleton05Temp);
    }

    private void clone06() throws CloneNotSupportedException {
        Singleton_06 singleton06 = Singleton_06.getInstance();

        Singleton_06 singleton06Temp = singleton06.clone();

        System.out.println("Singleton_06");
        System.out.println(singleton06);
        System.out.println(singleton06Temp);
    }

  • 解决方法:重写 clone() 方法,返回已经当前对象
  • 解决方案代码
    @Override
    public Singleton_01 clone() throws CloneNotSupportedException {
        // 解决克隆破坏单例模式
        return instance;
    }

3. 序列化破坏单例

  • 上述方法中(除第一种)仅有使用 Enum 枚举的第六种方法不会被破坏,其他单例模式均会被破坏
  • 测试方法代码
    @Test
    public void testSerializable() {
        try {
            System.out.println("testSerializable");
            /**
             * 都会被序列化破坏,但要注意类必须实现 Serializable 接口
             * 解决方法:添加 readResolve() 方法,返回当前对象,因为 ObjectInputStream.readObject() 会判断如果有此方法则直接调用此方法
             */
            Serializable01();
            Serializable02();
            Serializable03();
            Serializable04();
            Serializable06();
            // Enum 枚举类作为父类已实现了 Serializable 接口,允许序列化但是单例没有被破坏
            Serializable05();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void Serializable01() throws IOException, ClassNotFoundException {
        Singleton_01 singleton01 = Singleton_01.getInstance();

        // 对象序列化到文件
        SerializableObject(singleton01, "/Users/gusixue/Desktop/serializableTest.txt");
        // 对象反序列化回来
        Singleton_01 singleton01Temp = (Singleton_01) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");

        System.out.println("Singleton_01");
        System.out.println(singleton01);
        System.out.println(singleton01Temp);
    }

    private void Serializable02() throws IOException, ClassNotFoundException {
        Singleton_02 singleton02 = Singleton_02.getInstance();

        // 对象序列化到文件
        SerializableObject(singleton02, "/Users/gusixue/Desktop/serializableTest.txt");
        // 对象反序列化回来
        Singleton_02 singleton02Temp = (Singleton_02) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");

        System.out.println("Singleton_02");
        System.out.println(singleton02);
        System.out.println(singleton02Temp);
    }

    private void Serializable03() throws IOException, ClassNotFoundException {
        Singleton_03 singleton03 = Singleton_03.getInstance();

        // 对象序列化到文件
        SerializableObject(singleton03, "/Users/gusixue/Desktop/serializableTest.txt");
        // 对象反序列化回来
        Singleton_03 singleton03Temp = (Singleton_03) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");

        System.out.println("Singleton_03");
        System.out.println(singleton03);
        System.out.println(singleton03Temp);
    }

    private void Serializable04() throws IOException, ClassNotFoundException {
        Singleton_04 singleton04 = Singleton_04.getInstance();

        // 对象序列化到文件
        SerializableObject(singleton04, "/Users/gusixue/Desktop/serializableTest.txt");
        // 对象反序列化回来
        Singleton_04 singleton04Temp = (Singleton_04) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");

        System.out.println("Singleton_04");
        System.out.println(singleton04);
        System.out.println(singleton04Temp);
    }

    /**
     * Enum 枚举类作为父类已实现了 Serializable 接口
     * 允许序列化但是单例没有被破坏,因为调用 toString 方法返回的是 Enum 类的 name 参数的值,参数的值序列化前后不会改变
     */
    private void Serializable05() throws IOException, ClassNotFoundException {
        Singleton_05 singleton05 = Singleton_05.INSTANCE;

        // 对象序列化到文件
        SerializableObject(singleton05, "/Users/gusixue/Desktop/serializableTest.txt");
        // 对象反序列化回来
        Singleton_05 singleton05Temp = (Singleton_05) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");

        System.out.println("Singleton_05");
        System.out.println(singleton05);
        System.out.println(singleton05Temp);
    }

    private void Serializable06() throws IOException, ClassNotFoundException {
        Singleton_06 singleton06 = Singleton_06.getInstance();

        // 对象序列化到文件
        SerializableObject(singleton06, "/Users/gusixue/Desktop/serializableTest.txt");
        // 对象反序列化回来
        Singleton_06 singleton06Temp = (Singleton_06) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");

        System.out.println("Singleton_06");
        System.out.println(singleton06);
        System.out.println(singleton06Temp);
    }

    private void SerializableObject(Object object, String fileName) throws IOException {
        FileOutputStream fos = new FileOutputStream(fileName);
        @Cleanup
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(object);
        oos.flush();
        oos.close();
    }

    private Object deserializableObject(String fileName) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(fileName);
        @Cleanup
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object res = ois.readObject();
        ois.close();
        return res;
    }
  • 解决方法:添加 readResolve() 方法,返回当前对象,因为 ObjectInputStream.readObject() 会判断如果有此方法则直接调用此方法
  • 解决方案代码
    /**
     * 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    public Object readResolve() {
        return instance;
    }

参考:https://bugstack.cn/md/develop/design-pattern/2020-05-31-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E3%80%8B.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值