《单例模式的深度解读:实现方式、破坏情况与利弊权衡》

单例模式

一、单例模式的定义

​ 单例模式(Singleton Pattern)是一种常见的软件设计模式,确保一个类只有一个实例存在,并提供一个全局访问点来获取该实例。

二、单例模式的实现方式

​ 1.懒汉式单例

java 复制代码
public class LazySingleton {
    private static LazySingleton instance; // 静态变量存储唯一实例

    private LazySingleton() { // 私有构造函数,防止外部创建实例
        System.out.println("懒汉式单例构造函数被调用");
    }

    public static LazySingleton getInstance() { // 获取实例的静态方法
        if (instance == null) { // 如果实例尚未创建
            instance = new LazySingleton(); // 创建实例
            System.out.println("懒汉式单例实例首次创建");
        }
        return instance; // 返回实例
    }
}

​ 在懒汉式中,只有在第一次调用 getInstance 方法时才创建实例。

​ 2.饿汉式单例

java 复制代码
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton(); // 直接创建并初始化唯一实例

    private EagerSingleton() { // 私有构造函数
        System.out.println("饿汉式单例构造函数被调用");
    }

    public static EagerSingleton getInstance() { // 获取实例的静态方法
        System.out.println("饿汉式单例实例获取");
        return instance; 
    }
}

​ 饿汉式在类加载时就创建了实例。

​ 3.双重检验锁(Double Check Lock,DCL)

java 复制代码
private volatile static Singleton singleton;  // 使用volatile修饰,保证线程可见性
private Singleton (){}  // 私有构造函数,防止外部直接实例化

/**
 * 获取单例实例的方法
 * @return 单例对象
 */
public static Singleton getInstance() {
    if (singleton == null) {  // 第一次检查,避免不必要的同步
        synchronized (Singleton.class) {  // 同步锁,保证线程安全
            if (singleton == null) {  // 第二次检查,确保在同步块内只创建一次实例
                singleton = new Singleton();  // 创建单例实例
            }
        }
    }
    return singleton;  // 返回单例实例
}

​ DCL 模式的突出特点在于,它成功地在确保线程安全的基础上,于多线程环境中依然能够维持出色的性能表现。通过巧妙地进行两次 singleton 是否为 null 的判断,有效地规避了不必要的同步操作所带来的性能损耗。

​ 4.静态内部类

java 复制代码
private static class SingletonHolder {  // 静态内部类
    private static final Singleton INSTANCE = new Singleton();  // 在内部类中创建单例实例
}
private Singleton (){}  // 私有构造函数,防止外部直接实例化

/**
 * 获取单例实例的方法
 * @return 单例对象
 */
public static final Singleton getInstance() {
    return SingletonHolder.INSTANCE;  // 直接返回静态内部类中的单例实例
}

​ 它专门适用于静态域的场景。巧妙地借助了 Java 的类加载机制,仅在实际需要获取实例时才对静态内部类进行加载,从而顺利实现了延迟初始化的效果,同时也切实保障了线程的安全性。

​ 5.枚举

java 复制代码
public enum Singleton {  // 定义枚举类型的单例
    INSTANCE;  // 唯一的枚举值即单例实例
}

​ 它能够自动支持序列化机制,并且从根本上杜绝了多次实例化的可能性,是一种简洁、高效且极为可靠的单例实现手段。

三、破坏单例的情况及解决方法

  1. 反射破坏单例

    • 问题:通过反射可以绕过私有构造函数的限制创建新的实例。
    • 解决:在构造函数中进行判断,如果已经存在实例,抛出异常。
    java 复制代码
    /**
     * 四、破坏单例的情况及解决方法
     */
    
    /**
     * 反射破坏单例的解决示例
     * 问题:通过反射可以绕过私有构造函数的限制创建新的实例。
     * 解决:在构造函数中进行判断,如果已经存在实例,抛出异常。
     */
    public class SafeSingleton {
        private static SafeSingleton instance;  // 静态变量存储唯一实例
    
        /**
         * 私有构造函数
         * 在构造函数中检查是否已有实例存在,若有则抛出运行时异常
         */
        private SafeSingleton() {
            if (instance!= null) {
                throw new RuntimeException("单例模式,禁止通过反射创建多个实例!");
            }
        }
    
        /**
         * 获取单例实例的方法
         * 如果实例不存在则创建,存在则直接返回
         * @return 单例对象
         */
        public static SafeSingleton getInstance() {
            if (instance == null) {
                instance = new SafeSingleton();
            }
            return instance;
        }
    }
    
    /**
     * 序列化和反序列化破坏单例的解决示例
     * 问题:序列化后再反序列化可能创建新的实例。
     * 解决:实现 readResolve 方法返回已有的实例。
     */
    public class SerializableSingleton implements Serializable {
        private static final SerializableSingleton instance = new SerializableSingleton();  // 初始化唯一实例
    
        /**
         * 私有构造函数
         */
        private SerializableSingleton() {}
    
        /**
         * 获取单例实例的方法
         * @return 单例对象
         */
        public static SerializableSingleton getInstance() {
            return instance;
        }
    
        /**
         * 在反序列化时调用,返回已有的实例
         * @return 已有的单例实例
         */
        private Object readResolve() {
            return instance;
        }
    }
    
    /**
     * 克隆破坏单例的解决示例
     * 问题:若单例类实现了 Cloneable 接口,通过克隆可能创建新实例。
     * 解决:在 clone 方法中抛出异常或返回已有实例。
     */
    public class CloneableSingleton implements Cloneable {
        private static CloneableSingleton instance = new CloneableSingleton();  // 存储唯一实例
    
        /**
         * 私有构造函数
         */
        private CloneableSingleton() {}
    
        /**
         * 获取单例实例的方法
         * @return 单例对象
         */
        public static CloneableSingleton getInstance() {
            return instance;
        }
    
        /**
         * 重写 clone 方法,抛出不支持克隆的异常
         * @return 抛出异常,禁止克隆
         * @throws CloneNotSupportedException 不支持克隆异常
         */
        @Override
        protected Object clone() throws CloneNotSupportedException {
            throw new CloneNotSupportedException("单例模式,禁止克隆!");
        }
    }

    四、总结

    适用场景

    单例模式适用于在任何情况下都绝对只需要一个实例的情况,例如 ServletContextServletConfigApplicationContextDBPoolThreadPool 等。

    优点

    1. 在内存中仅存在一个实例,显著降低了内存开销。
    2. 能够有效避免对资源的多重占用,保证资源的合理分配和使用。
    3. 设定了全局访问点,从而实现了对访问的严格管控。

    缺点

    1. 没有提供接口,导致扩展较为困难。
    2. 若要对单例对象进行扩展,只能通过修改代码来实现,缺乏其他灵活的途径。

    总的来说,单例模式在特定场景下能够发挥其优势,有效地管理资源和控制访问,但在扩展性方面存在一定的局限性。在实际应用中,需要根据具体需求权衡其利弊,选择是否使用单例模式。

相关推荐
蜡笔小新星29 分钟前
Flask项目框架
开发语言·前端·经验分享·后端·python·学习·flask
IT猿手30 分钟前
2025最新群智能优化算法:海市蜃楼搜索优化(Mirage Search Optimization, MSO)算法求解23个经典函数测试集,MATLAB
开发语言·人工智能·算法·机器学习·matlab·机器人
夏天的味道٥3 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
IT、木易5 小时前
大白话JavaScript实现一个函数,将字符串中的每个单词首字母大写。
开发语言·前端·javascript·ecmascript
冰糖码奇朵5 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好5 小时前
【Spring】整合【SpringMVC】
java·spring
Mr.NickJJ5 小时前
JavaScript系列06-深入理解 JavaScript 事件系统:从原生事件到 React 合成事件
开发语言·javascript·react.js
浪九天6 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
Archer1947 小时前
C语言——链表
c语言·开发语言·链表
My Li.7 小时前
c++的介绍
开发语言·c++