设计模式-3.1单例模式


📝 所有单例的实现都包含以下相同的步骤:

  • 📌 将默认构造函数设为私有, 防止其他对象使用单例类的 new 运算符。
  • 📌 新建一个静态构建方法作为构造函数。
  • ⚠️ 注意: 该函数会 “偷偷” 调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。
  • ⚠️ 注意: 此后所有对于该函数的调用都将返回这一缓存对象。

📝 单例设计模式分类两种(枚举也是懒汉式):

  • 🚀 饿汉式:

    • 📌 类加载就会导致该单实例对象被创建。
  • 🛌 懒汉式:

    • 📌 类加载不会导致该单实例对象被创建。
    • ⚠️ 注意: 首次使用该对象时才会创建(这就是懒加载)。
  • 🎖 枚举方式(推荐):

    • 📌 枚举类型是线程安全的,并且只会装载一次。
    • 📌 枚举的写法非常简单。
    • ⚠️ 注意: 可以利用枚举的特性来解决线程安全和单一实例的问题。
    • ⚠️ 注意: 还可以防止反射和反序列化对单例的破坏。

饿汉式懒汉式是两种常见的单例模式创建方式,这些方式用于确保一个类只有一个实例,并提供一种全局访问点以访问该实例。

  • 饿汉式(Eager Initialization)
    • 📌 在饿汉式中,实例在类加载时就被创建。
    • 📌 优点是在多线程环境下是线程安全的。
    • ⚠️ 注意: 可能会浪费一些内存。
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    
    private EagerSingleton() {
        // Private constructor to prevent instantiation.
    }
    
    public static EagerSingleton getInstance() {
        return instance;
    }
}
  • 懒汉式(Lazy Initialization)
    • 📌 在懒汉式中,实例只有在第一次被访问时才会被创建。
    • 📌 优点是节省了内存。
    • ⚠️ 注意: 需要考虑线程安全性。
public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {
        // Private constructor to prevent instantiation.
    }
    
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

⚠️ 注意: 上述的简单懒汉式实现虽然线程安全,但在高并发情况下,每次调用 getInstance 都需要加锁,可能影响性能。

⚠️ 注意: 单例模式在某些情况下可能不是最佳的设计选择,因为它会引入全局状态,可能会导致耦合和测试困难。


📝 存在的问题(枚举方式不存在的)


  • 🚫 内存浪费:

    • 📌 适用于: 饿汉式
    • ⚠️ 注意: 由于实例在类加载时就被创建,即使后续可能并未使用,也会占用内存。
  • 🚫 线程安全:

    • 📌 适用于: 懒汉式
    • ⚠️ 注意: 在多线程环境下,没有采取额外的措施可能会导致创建多个实例。
  • 🚫 反序列化破坏:

    • 📌 适用于: 大多数单例实现方式。
    • ⚠️ 注意: 反序列化会调用反射,多次反序列化产生的对象是不一致的。
    • ⚠️ 注意: 序列化多次保存相同的对象时,实际保存的只是第一个对象的引用。
  • 🚫 反射破坏:

    • 📌 适用于: 大多数单例实现方式。
    • ⚠️ 注意: 通过反射的方式可以调用私有的构造方法,从而破坏单例原则。

📝 几种优秀的单例模式代码示例


📌 双重检查锁方式(可以被序列化和反射破化)

package com.study.notes.design.patterns.pattern.create.singleton.demo4;

/**
 * @version v1.0
 * @ClassName: Singleton
 * @Description: 双重检查锁方式
 * 双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,
 * 其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指今重排序操作。
 * 要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字,volatile 关键字可以保证可见性和有序性。
 * volatile是Java虚拟机提供的轻量级同步机制。
 * 保证可见性
 * 不保证原子性
 * 禁止指令重排(保证有序性)
 * @Author: lzq
 */
public class Singleton {

    //声明Singleton类型的变量
    private static volatile Singleton instance;

    //私有构造方法
    private Singleton() {
    }

    //对外提供公共的访问方式
    public static Singleton getInstance() {
        //第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if (instance == null) {
            synchronized (Singleton.class) {
                //第二次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}


📌 静态内部类(加入了防止序列化和反射破化的代码)

package com.study.notes.design.patterns.pattern.create.singleton.demo7;

import java.io.Serializable;

/**
 * @version v1.0
 * @ClassName: Singleton
 * @Description: 静态内部类方式
 * 序列化、反序列方式破坏单例模式的解决方法
 * 在Singleton类中添加readResolve方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
 * @Author: lzq
 */
public class Singleton implements Serializable {

    private static boolean flag = false;

    //私有构造方法
    private Singleton() {
        synchronized (Singleton.class) {
            //判断flag的值是否是true,如果是true,说明非第一次访问,直接抛一个异常,如果是false的话,说明第一次访问
            if (flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            //将flag的值设置为true
            flag = true;
        }
    }

    //提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    //当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve() {
        return SingletonHolder.INSTANCE;
    }

    //定义一个静态内部类
    private static class SingletonHolder {
        //在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

}


📌 枚举形式(最好的)

package com.study.notes.design.patterns.pattern.create.singleton.demo6;

/**
 * @version v1.0
 * @ClassName: Singleton
 * @Description: 枚举实现方式
 * 枚举类型是线程安全的,并且只会装载一次,枚举的写法非常简单,可以利用枚举的特性来解决线程安全和单一实例的问题,
 * 还可以可以防止反射和反序列化对单例的破坏,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
 * <p>
 * 在代码中,我们首先将Singleton类的构造函数设置为private私有的,然后在Singleton类中定义一个静态的枚举类型SingletonEnum。
 * 在SingletonEnum中定义了枚举类型的实例对象Singleton,再按照单例模式的要求在其中定义一个Singleton类型的对象instance,其初始值为null;
 * 我们需要将SingletonEnum的构造函数改为私有的,在私有构造函数中创建一个Singleton的实例对象;最后在getInstance()方法中返回该对象。
 * 在实现过程中,Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。
 * * @Author: lzq
 */
public class Singleton {
    private Singleton() {
    }

    public static enum SingletonEnum {
        SINGLETON;
        private Singleton instance = null;

        private SingletonEnum() {
            instance = new Singleton();
        }

        public Singleton getInstance() {
            return instance;
        }
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yueerba126

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值