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

news2025/2/23 6:15:41

在这里插入图片描述

目录

    • 一、啥是单例模式?
    • 二、为什么要用单例模式?
    • 三、单例模式怎么实现?
      • 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.coloradmin.cn/o/2303784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动化办公|xlwings生成图表

在日常的数据分析和报告生成中,Excel图表是一个非常重要的工具。它能够帮助我们直观地展示数据,发现数据中的规律和趋势。然而,手动创建和调整图表往往耗时且容易出错。幸运的是,借助Python的xlwings库,我们可以自动化…

Docker基于Ollama本地部署大语言模型

一、Ollama介绍 Ollama 是一个开源的大型语言模型(LLM)平台,旨在简化大型语言模型在本地环境中的运行、管理和交互。通过Ollama,用户可以轻松加载和使用各种预训练的语言模型,执行诸如文本生成、翻译、代码编写、问答…

centos9安装k8s集群

以下是基于CentOS Stream 9的Kubernetes 1.28.2完整安装流程(containerd版): 一、系统初始化(所有节点执行) # 关闭防火墙 systemctl disable --now firewalld# 关闭SELinux sed -i "s/SELINUXenforcing/SELINU…

pytest下allure

import pytestdef test_case01():用例01~print(用例01)class Test_mokuai01:def test_case02(self):用例02~print(用例02)if __name____main__:#pytest.main([-vs,test_sample-2.py])pytest.main([-vs,test_sample-2.py,--allure-dir,./result2])#生成allure报告,参…

JVM预热

阿里电商平台每年的各种大促活动,对于Java技术来说,其中重要一个操作环节就是预热操作。 目录 预热是什么?为什么要预热? java 程序不预热和预热的调用对比 预热是什么? 预热是指,在 JVM 启动后&#xff0…

【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?

文章目录 🌍一. 数据交换--JSON❄️1. JSON介绍❄️2. JSON 快速入门❄️3. JSON 对象和字符串对象转换❄️4. JSON 在 java 中使用❄️5. 代码演示 🌍二. 异步请求--Ajax❄️1. 基本介绍❄️2. JavaScript 原生 Ajax 请求❄️3. JQuery 的 Ajax 请求 &a…

网页制作06-html,css,javascript初认识のhtml如何建立超链接

超链接有外部链接、电子邮件链接、锚点链接、空链接、脚本链接 一、内部链接 与自身网站页面有关的链接被称为内部链接 1、创建内部链接 1&#xff09;语法&#xff1a; <a href"链接地址"> …… </a> 2&#xff09;举例应用&#xff1a; 3&#xf…

代码讲解系列-CV(七)——前沿论文复现

文章目录 一、论文速览1.1 确定baseline1.2 DepthMaster: Taming Diffusion Models for Monocular Depth Estimation 二、数据环境搭建2.1 环境搭建2.2 数据权重 三、推理debug3.1 单图推理3.2 数据集验证 四、模型训练4.1 数据读取4.2 训练流程 五、作业 一、论文速览 1.1 确…

数据库面试知识点总结

目录 1. MySQL 基础题1.1 执行⼀条 select / update 语句&#xff0c;在 MySQL 中发生了什么&#xff1f;1.2 MySQL 一行记录是怎么存储的&#xff1f; 2. 三大范式3. 数据库引擎3.1 Innodb3.2 MyISAM 4. 数据库索引4.1 索引分类4.2 索引优缺点4.3 索引使用场景4.4 优化索引方法…

1.25作业

1easytornado SSTI——tornado模板 hints.txt&#xff1a;在/fllllllllllllag里&#xff1b;计算filehash的方法&#xff08;需要cookie_secret,对filename进行md5拼接再第二次md5&#xff09; ?filename/hints.txt&filehash{ {2*3}}&#xff0c;跳转到另一个页面 存在且…

Power Query M函数

文章目录 三、PQ高阶技能&#xff1a;M函数3.1 M函数基本概念3.1.1 表达式和值3.1.2 计算3.1.3 运算符3.1.4 函数3.1.5 元数据3.1.6 Let 表达式3.1.6 If 表达式3.1.7 Error 3.2 自定义M函数3.2.1 语法3.2.2 调用定义好的自定义函数3.2.3 直接调用自定义函数3.2.4 自定义函数&am…

python argparse 解析命令行参数

可选参数 带 - 或者 -- 的参数都是可选参数&#xff0c;如果命令行不输入&#xff0c;得到的结果是 None 参数名只能使用下划线&#xff0c;不能使用中划线 default&#xff1a; 设置默认值 action&#xff1a; 默认是 store 方法&#xff0c;常用的是 store_true 命令行出…

【网络编程】服务器模型(二):并发服务器模型(多线程)和 I/O 复用服务器(select / epoll)

一、多线程并发服务器 在 高并发的 TCP 服务器 中&#xff0c;单线程或 fork() 多进程 方式会导致 资源浪费和性能瓶颈。因此&#xff0c;我们可以使用 多线程 来高效处理多个客户端的连接。 承接上文中的多进程并发服务器&#xff0c;代码优化目标&#xff1a; 1.使用 pthr…

自学Java-AI结合GUI开发一个石头迷阵的游戏

自学Java-AI结合GUI开发一个石头迷阵的游戏 准备环节1、创建石头迷阵的界面2、打乱顺序3、控制上下左右移动4、判断是否通关5、统计移动步骤&#xff0c;重启游戏6、拓展问题 准备环节 技术&#xff1a; 1、GUI界面编程 2、二维数组 3、程序流程控制 4、面向对象编程 ∙ \bulle…

Liunx(CentOS-6-x86_64)系统安装MySql(5.6.50)

一&#xff1a;安装Liunx&#xff08;CentOS-6-x86_64&#xff09; 安装Liunx&#xff08;CentOS-6-x86_64&#xff09; 二&#xff1a;下载MySql&#xff08;5.6.50&#xff09; MySql下载官网 二&#xff1a;安装MySql 2.1 将mysql上传到Liunx 文件地址 /usr/local/ 2…

Java Web开发实战与项目——开发一个在线论坛系统

在线论坛系统是一个常见的Web应用&#xff0c;通常具有用户注册、帖子发布、评论互动、消息推送等基本功能。开发这样一个系统&#xff0c;既涉及到前后端的技术栈选择&#xff0c;也需要考虑性能、扩展性等实际问题。本文将从设计论坛模块、实现消息推送与实时更新功能、以及优…

ubuntu24.04无法安装向日葵,提示依赖libgconf-2-4怎么办?

在向日葵官方下载的deb包&#xff0c;目前是SunloginClient_15.2.0.63062_amd64.deb&#xff0c;执行安装代码&#xff0c;如下&#xff1a; sudo < /span > dpkg< /span > -i< /span > SunloginClient_15< /span >.2< /span >.0< /span >…

Kubernetes 使用 Kube-Prometheus 构建指标监控 +飞书告警

1 介绍 Prometheus Operator 为 Kubernetes 提供了对 Prometheus 机器相关监控组件的本地部署和管理方案&#xff0c;该项目的目的是为了简化和自动化基于 Prometheus 的监控栈配置&#xff0c;主要包括以下几个功能&#xff1a; Kubernetes 自定义资源&#xff1a;使用 Kube…

WPF的页面设计和实用功能实现

目录 一、TextBlock和TextBox 1. 在TextBlock中实时显示当前时间 二、ListView 1.ListView显示数据 三、ComboBox 1. ComboBox和CheckBox组合实现下拉框多选 四、Button 1. 设计Button按钮的边框为圆角&#xff0c;并对指针悬停时的颜色进行设置 一、TextBlock和TextBox…

window安装MySQL5.7

1、下载MySQL5.7.24 浏览器打开&#xff1a; https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.24-winx64.zip 2、解压缩 下载下来的是一个压缩包&#xff0c;解压到你想放到的目录下面&#xff0c;我放的是“C:\MySQL” 3、配置MySQL环境变量 计算机右键 - 属性 …