(一)趣学设计模式 之 单例模式!

news/2025/2/22 6:24:44

在这里插入图片描述

目录

    • 一、啥是单例模式
    • 二、为什么要用单例模式
    • 三、单例模式怎么实现?
      • 1. 饿汉式:先下手为强! 😈
      • 2. 懒汉式:用的时候再创建! 😴
      • 3. 枚举:最简单最安全的单例! 😎
    • 四、单例模式的应用场景
    • 五、单例模式的破坏与防御
    • 六、总结

🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟比如: synchronized 关键字:线程同步的“VIP 包间”

这篇文章带你详细认识一下设计模式中的单例模式

一、啥是单例模式

想象一下,你有一个特别宝贝的遥控器 🎮,只能控制你家的电视。如果家里有好多遥控器,那不就乱套了吗?单例模式就像这个遥控器一样,保证一个类只能创建一个对象,而且这个对象是全局唯一的!

简单来说,单例模式就是:一个类只有一个实例,而且到处都能访问它!

二、为什么要用单例模式

  • 节省资源: 有些对象创建起来很耗费资源,比如数据库连接池 🏊‍♀️,如果每次都创建新的,那得多浪费啊!单例模式可以保证只创建一个,大家共享着用。
  • 保证数据一致性: 有些数据需要全局唯一,比如配置信息 ⚙️,如果每个地方都有一份,那万一改了其中一份,其他地方不知道,就出问题了!单例模式可以保证大家访问的是同一份数据。
  • 方便管理: 有些对象需要全局管理,比如日志记录器 📝,如果每个地方都创建一个,那日志文件就乱七八糟了!单例模式可以保证只有一个地方负责记录日志。

三、单例模式怎么实现?

单例模式有很多种实现方式,我们一个个来看:

1. 饿汉式:先下手为强! 😈

饿汉式就像一个急性子,在类加载的时候就创建好对象了,不管你用不用,它都先准备好!

  • 方式1:静态常量

    public class Singleton {
        // 1. 私有构造方法,防止别人乱new 🙅‍♀️
        private Singleton() {
            System.out.println("Singleton 构造方法被调用了!"); // 看看啥时候被调用的
        }
    
        // 2. 在内部创建一个静态常量,直接new一个对象 👶
        private static final Singleton instance = new Singleton();
    
        // 3. 提供一个公共的静态方法,让别人来拿这个对象 🤝
        public static Singleton getInstance() {
            System.out.println("getInstance() 方法被调用了!"); // 看看啥时候被调用的
            return instance;
        }
    
        public void doSomething() {
            System.out.println("Singleton 对象正在工作! 👷‍♀️");
        }
    
        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            s1.doSomething();
            Singleton s2 = Singleton.getInstance(); // 再次获取
            System.out.println(s1 == s2); // 看看是不是同一个对象
        }
    }
    

    输出结果:

    Singleton 构造方法被调用了!  // 类加载时就调用了
    getInstance() 方法被调用了!
    Singleton 对象正在工作! 👷‍♀️
    getInstance() 方法被调用了!
    true  // s1 和 s2 是同一个对象
    

    优点: 实现简单,线程安全,不用担心多线程问题。
    缺点: 类加载的时候就创建对象,如果一直不用,就浪费内存了 😥。

  • 方式2:静态代码块

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton 构造方法被调用了!");
        }
    
        private static Singleton instance;
    
        static {
            System.out.println("静态代码块被执行了!");
            instance = new Singleton();
        }
    
        public static Singleton getInstance() {
            System.out.println("getInstance() 方法被调用了!");
            return instance;
        }
    
        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
            System.out.println(s1 == s2);
        }
    }
    

    输出结果:

    静态代码块被执行了!
    Singleton 构造方法被调用了!
    getInstance() 方法被调用了!
    getInstance() 方法被调用了!
    true
    

    说明: 这种方式和静态常量方式差不多,都是在类加载的时候创建对象,优缺点也一样。

2. 懒汉式:用的时候再创建! 😴

懒汉式就像一个懒家伙,只有在你需要的时候才创建对象,实现了延迟加载!

  • 方式1:线程不安全

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton 构造方法被调用了!");
        }
    
        private static Singleton instance;
    
        public static Singleton getInstance() {
            System.out.println("getInstance() 方法被调用了!");
            if (instance == null) {
                System.out.println("instance 为 null,准备创建对象!");
                instance = new Singleton();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
            System.out.println(s1 == s2);
        }
    }
    

    输出结果:

    getInstance() 方法被调用了!
    instance 为 null,准备创建对象!
    Singleton 构造方法被调用了!
    getInstance() 方法被调用了!
    true
    

    优点: 实现了延迟加载,节省了内存。
    缺点: 在多线程环境下,不安全!多个线程可能同时进入 if (instance == null),导致创建多个对象 💥。

  • 方式2:线程安全(同步方法)

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton 构造方法被调用了!");
        }
    
        private static Singleton instance;
    
        public static synchronized Singleton getInstance() { // 加了 synchronized 关键字
            System.out.println("getInstance() 方法被调用了!");
            if (instance == null) {
                System.out.println("instance 为 null,准备创建对象!");
                instance = new Singleton();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
            System.out.println(s1 == s2);
        }
    }
    

    优点: 解决了线程安全问题。
    缺点: 性能太差!每次调用 getInstance() 都要加锁,太慢了 🐌。

  • 方式3:双重检查锁(Double-Checked Locking)

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton 构造方法被调用了!");
        }
    
        private static volatile Singleton instance; // volatile 保证可见性和有序性
    
        public static Singleton getInstance() {
            System.out.println("getInstance() 方法被调用了!");
            if (instance == null) { // 第一次检查
                synchronized (Singleton.class) { // 加锁
                    System.out.println("进入 synchronized 代码块!");
                    if (instance == null) { // 第二次检查
                        System.out.println("instance 仍然为 null,准备创建对象!");
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    
        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
            System.out.println(s1 == s2);
        }
    }
    

    优点: 兼顾了线程安全和性能,只有在第一次创建对象的时候才加锁。
    缺点: 实现比较复杂,需要 volatile 关键字来防止指令重排序。

  • 方式4:静态内部类

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton 构造方法被调用了!");
        }
    
        // 静态内部类
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton(); // 在内部类中创建实例
            static {
                System.out.println("SingletonHolder 静态代码块被执行了!");
            }
        }
    
        public static Singleton getInstance() {
            System.out.println("getInstance() 方法被调用了!");
            return SingletonHolder.INSTANCE; // 返回内部类的实例
        }
    
        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
            System.out.println(s1 == s2);
        }
    }
    

    输出结果:

    getInstance() 方法被调用了!
    SingletonHolder 静态代码块被执行了!
    Singleton 构造方法被调用了!
    getInstance() 方法被调用了!
    true
    

    优点: 线程安全,延迟加载,实现简单,强烈推荐! 👍
    缺点: 稍微有点难理解。

3. 枚举:最简单最安全的单例! 😎

public enum Singleton {
    INSTANCE; // 唯一的实例

    public void doSomething() {
        System.out.println("枚举单例正在工作! 💪");
    }

    public static void main(String[] args) {
        Singleton.INSTANCE.doSomething();
    }
}

优点: 实现简单,线程安全,防止反射攻击和序列化攻击,绝对安全! 💯
缺点: 不能延迟加载。

四、单例模式的应用场景

  • 数据库连接池 🏊‍♀️: 保证只有一个连接池,避免资源浪费。
  • 配置管理器 ⚙️: 保证配置信息全局唯一,避免数据不一致。
  • 日志记录器 📝: 保证只有一个日志记录器,方便管理日志文件。
  • 任务管理器 TaskManager: 保证只有一个任务管理器,避免任务冲突。

五、单例模式的破坏与防御

单例模式虽然好,但是也可能被破坏!

  • 反射攻击: 通过反射可以调用私有构造方法,创建多个实例。
  • 序列化攻击: 通过序列化和反序列化可以创建多个实例。

防御方法:

  • 在构造方法中判断实例是否已经存在,如果存在则抛出异常。
  • 在单例类中添加 readResolve() 方法,在反序列化时返回已存在的实例。
  • 使用枚举单例,天然防止反射攻击和序列化攻击!

六、总结

  • 单例模式保证一个类只有一个实例,并提供一个全局访问点。
  • 有很多种实现方式,各有优缺点。
  • 要根据具体场景选择合适的实现方式。
  • 要注意防止单例模式被破坏。
  • 枚举单例是最简单最安全的单例实现方式!

希望这篇文章能让你彻底理解单例模式! 👍


http://www.niftyadmin.cn/n/5861784.html

相关文章

leetcode刷题第十三天——二叉树Ⅲ

本次刷题顺序是按照卡尔的代码随想录中给出的顺序 翻转二叉树 226. 翻转二叉树 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*//*总体思路就是,对于每一个结点&…

如何利用 Vue 的生命周期钩子进行初始化和清理操作?

一、初始化操作的核心钩子 1. created(选项式API) export default {data() {return { user: null };},created() {// 适合初始化数据、发起非DOM操作请求this.fetchUser();},methods: {async fetchUser() {const response await fetch(/api/user);thi…

什么容错性以及Spark Streaming如何保证容错性

一、容错性的定义 容错性是指一个系统在发生故障或崩溃时,能够继续运行并提供一定服务的能力。在网络或系统中,这通常涉及到物理组件损坏或软件失败时系统的持续运行能力。容错系统的关键特性包括负载平衡、集群、冗余、复制和故障转移等。 二、Spark …

win10把c盘docker虚拟硬盘映射迁移到别的磁盘

c盘空间本身就比较小、如果安装了docker服务后,安装的时候没选择其他硬盘,虚拟磁盘也在c盘会占用很大的空间,像我的就三十多个G,把它迁移到其他磁盘一下子节约几十G 1、先输入下面命令查看 docker 状态 wsl -l -v 2、如果没有停止…

oracle怎么创建定时任务

在Oracle中创建定时任务,可以使用DBMS_SCHEDULER包,以下是创建定时任务的详细步骤: 1. 创建作业 需要创建一个作业,用于执行定时任务,作业是一组SQL语句或PL/SQL代码,可以定期执行。 BEGINDBMS_SCHEDULE…

从DeepSeek大爆发看AI革命困局:大模型如何突破算力囚笼与信任危机?

目录 从DeepSeek大爆发看AI革命困局:大模型如何突破算力囚笼与信任危机? 小瓜有话说——为什么想写这篇博文 一、算力军备竞赛下的临时繁荣 1、技术奇点的陷阱 2、行业困境 二、三类企业的生存实验 1. 守旧派的黄昏:参数崇拜者的绝击 …

pycharm将当前项目上传到github

要将当前项目从 PyCharm 上传到 GitHub,你可以按照以下步骤操作: 1. 创建一个 GitHub 仓库 登录到 GitHub。点击右上角的 按钮,然后选择 New repository。填写仓库名称、描述等信息,点击 Create repository。 2. 在 PyCharm 中…

C++ 设计模式-备忘录模式

游戏存档实现&#xff0c;包括撤销/重做、持久化存储、版本控制和内存管理 #include <iostream> #include <memory> #include <deque> #include <stack> #include <chrono> #include <fstream> #include <sstream> #include <ct…