设计模式-单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,其核心目标是确保一个类只有一个实例,并提供全局访问点。

单例模式的核心实现要点

  1. 私有构造函数 :防止外部直接通过 new 创建实例。
  2. 静态实例:保存类的唯一实例。
  3. 全局访问点:提供获取实例的静态方法。
1、饿汉式

特点:类加载时直接初始化实例,线程安全,但可能浪费资源。

java 复制代码
 /**
     * 饿汉式单例
     * 优点:实现简单,线程安全
     * 缺点:类加载时就初始化实例,可能浪费内存
     */
    public class EagerSingleton {
        // 静态常量直接初始化实例,保证线程安全
        private static final EagerSingleton INSTANCE = new EagerSingleton();
    
        // 私有构造函数,防止外部实例化
        private EagerSingleton() {}
    
        // 全局访问点
        public static EagerSingleton getInstance() {
            return INSTANCE;
        }
    }
2、懒汉式

特点:延迟加载实例,但是需要通过同步锁保证线程安全(效率低)。

java 复制代码
/**
 * 懒汉式单例(同步方法)
 * 优点:延迟加载,节省资源
 * 缺点:同步锁导致性能下降
 */
public class LazySingleton {
    private static LazySingleton instance;

    // 私有构造函数
    private LazySingleton() {}

    // 同步方法保证线程安全,但每次调用都加锁
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
3、双重校验

特点:在懒汉式的基础上,降低锁的粒度,提高效率

java 复制代码
/**
 * 双重检查锁定单例
 * 特点:在懒汉式的基础上,降低锁的粒度,减少锁的等待时间
 */
public class Singleton_03 {
    // volatile 禁止指令重排序,保证可见性
    private static volatile Singleton_03 instance;

    private Singleton_03() {
    }
    public static Singleton_03 getInstance() {
        // 第一次检查(无锁)
        if (instance == null) {
            synchronized (Singleton_03.class) {
                // 第二次检查(有锁)
                if (instance == null) {
                    instance = new Singleton_03();
                    /*
                     * new 操作分为三步:
                     * 1. 分配内存空间
                     * 2. 初始化对象
                     * 3. 将引用指向内存地址
                     * volatile 防止指令重排序到1 → 3 → 2 的情况
                     */
                }
            }
        }
        //如果没有volatile 在多线程环境下会返回一个次品对象
        return instance;
    }
}
4、静态内部类

特点:利用类加载机制保证线程安全,天然支持延迟加载。

java 复制代码
/**
 * 静态内部类单例
 * 优点:线程安全、延迟加载、无锁高效
 * 缺点:无法通过参数初始化实例
 */
public class InnerClassSingleton {
    // 私有构造函数
    private InnerClassSingleton() {
    }

    // 静态内部类持有实例
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }

    // 全局访问点
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE; // 首次调用时加载内部类
    }
}
5、枚举

特点:简洁、线程安全,天然防御反射和序列化攻击(推荐方式)。

java 复制代码
/**
 * 枚举单例(推荐)
 * 优点:天然线程安全,防御反射和序列化攻击
 * 缺点:无法延迟加载
 */
public enum EnumSingleton {
    INSTANCE; // 枚举常量即为单例实例

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

反射的核心功能

  1. 运行时获取类信息:无需提前知道类名,动态加载类。
  2. 操作对象:动态创建对象、调用方法、访问字段。
  3. 突破访问限制 :访问私有(private)成员。
1. 防御反射攻击
  • 问题:通过反射调用私有构造函数可以创建新实例。
  • 解决方案:在构造函数中检查实例是否已存在,若存在则抛出异常。
2. 防御序列化攻击
  • 问题:反序列化会生成新对象。
  • 解决方案 :实现 readResolve() 方法返回已有实例。
反射对于单例的破坏
java 复制代码
/**
 * 反射对于单例的破坏
 */
public class Test_Reflect {
    public static void main(String[] args) throws Exception {
        Class<InnerClassSingleton> clazz = InnerClassSingleton.class;
        Constructor<InnerClassSingleton> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        InnerClassSingleton instance = constructor.newInstance();
        InnerClassSingleton instance2 = constructor.getInstance();
        System.out.println(instance == instance2);
    }
}

false
解决方式

在构造器添加一个判断语句

java 复制代码
 // 私有构造函数
    private InnerClassSingleton() {
        // 防御反射攻击
        if (SingletonHolder.INSTANCE != null) {
            throw new RuntimeException("单例对象已存在!");
        }
    }
序列化对于单例的破坏
java 复制代码
public class Test_Serializable {
    @Test
    public void test() throws Exception{
        //序列化对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
        oos.writeObject(InnerClassSingleton.getInstance());

        //反序列化对象输入流
        File file = new File("tempFile.obj");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        InnerClassSingleton instance = (InnerClassSingleton) ois.readObject();

        System.out.println(instance);
        System.out.println(InnerClassSingleton.getInstance());

        System.out.println(instance == InnerClassSingleton.getInstance());
    }
}
//输出
com.pgs.singleton.demo04.Singleton_04@3c9d0b9d
com.pgs.singleton.demo04.Singleton_04@6dbb137d
false
解决方式

只需要在单例类中定义 readResolve 方法,就可以解决序列化对于单例的破坏

java 复制代码
   // 防止反序列化破坏单例
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
相关推荐
Mr.Jessy1 天前
JavaScript高级:构造函数与原型
开发语言·前端·javascript·学习·ecmascript
云栖梦泽1 天前
鸿蒙应用签名与上架全流程:从开发完成到用户手中
开发语言·鸿蒙系统
爱上妖精的尾巴1 天前
6-4 WPS JS宏 不重复随机取值应用
开发语言·前端·javascript
Goldn.1 天前
Java核心技术栈全景解析:从Web开发到AI融合
java· spring boot· 微服务· ai· jvm· maven· hibernate
李慕婉学姐1 天前
【开题答辩过程】以《基于Android的出租车运行监测系统设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·后端·vue
小鸡吃米…1 天前
Python 列表
开发语言·python
m0_740043731 天前
SpringBoot05-配置文件-热加载/日志框架slf4j/接口文档工具Swagger/Knife4j
java·spring boot·后端·log4j
编织幻境的妖1 天前
SQL查询连续登录用户方法详解
java·数据库·sql
kaikaile19951 天前
基于C#实现一维码和二维码打印程序
开发语言·c#
我不是程序猿儿1 天前
【C#】画图控件的FormsPlot中的Refresh功能调用消耗时间不一致缘由
开发语言·c#