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

在这里插入图片描述

目录

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nacos编写瀚高数据库插件

1、下载nacos源码 git clone gitgithub.com:alibaba/nacos.git 2、引入瀚高驱动 <dependency><groupId>com.highgo</groupId><artifactId>jdbc</artifactId><version>${highgo.version}</version></dependency> 3、DataSource…

讯飞唤醒+VOSK语音识别+DEEPSEEK大模型+讯飞离线合成实现纯离线大模型智能语音问答。

在信息爆炸的时代&#xff0c;智能语音问答系统正以前所未有的速度融入我们的日常生活。然而&#xff0c;随着数据泄露事件的频发&#xff0c;用户对于隐私保护的需求日益增强。想象一下&#xff0c;一个无需联网、即可响应你所有问题的智能助手——这就是纯离线大模型智能语音…

后端Java Stream数据流的使用=>代替for循环

API讲解 对比 示例代码对比 for循环遍历 package cn.ryanfan.platformback.service.impl;import cn.ryanfan.platformback.entity.Algorithm; import cn.ryanfan.platformback.entity.AlgorithmCategory; import cn.ryanfan.platformback.entity.DTO.AlgorithmInfoDTO; im…

UE 播放视频

一.UI播放视频 1.导入视频文件至工程文件夹 2.文件夹内右健选择Media -> File Meida Source创建testFileMeidaSource文件。 编辑FilePath为当前视频 3.右键->Media->Media Player 创建testMediaPlayer文件 4.右键创建testMediaTexture。编辑MediaPlayer设置testMedia…

推荐一款AI大模型托管平台-OpenWebUI

推荐一款AI大模型托管平台-OpenWebUI 1. OpenWebUI 1. OpenWebUI什么? 官网地址&#xff1a;https://openwebui.com/ GitHub地址&#xff1a; https://github.com/open-webui/open-webui Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台&#xff0c;旨在完全离…

【Python项目】基于知识图谱的百科问答系统

【Python项目】基于知识图谱的百科问答系统 技术简介&#xff1a; 采用Python技术、MySQL数据库、Django框架、Scrapy爬虫等技术实现。 系统简介&#xff1a; 百科问答系统是一个基于知识图谱的问答平台&#xff0c;旨在为用户提供快速、准确的百科知识查询服务。系统通过爬…

stm32rtc实时时钟详解文章

目录 stm32 后备区域基础知识详解 stm32 bkp基础知识详解 Unix时间戳基础知识详解 stm32 rtc实时时钟基础知识详解 相关代码初始化配置 欢迎指正&#xff0c;希望对你&#xff0c;有所帮助&#xff01;&#xff01;&#xff01; stm32 后备区域基础知识详解 stm32芯片的 …

Spring Boot项目@Cacheable注解的使用

Cacheable 是 Spring 框架中用于缓存的注解之一&#xff0c;它可以帮助你轻松地将方法的结果缓存起来&#xff0c;从而提高应用的性能。下面详细介绍如何使用 Cacheable 注解以及相关的配置和注意事项。 1. 基本用法 1.1 添加依赖 首先&#xff0c;确保你的项目中包含了 Spr…

windows上vscode cmake工程搭建

安装vscode插件&#xff1a; 1.按装fastc&#xff08;主要是安装MinGW\mingw64比较方便&#xff09; 2.安装C&#xff0c;cmake&#xff0c;cmake tools插件 3.准备工作完成之后&#xff0c;按F1&#xff0c;选择cmake:Quick Start就可以创建一个cmake工程。 4.设置Cmake: G…

SpringMVC详解

文章目录 1 什么是MVC 1.1 MVC设计思想1.2 Spring MVC 2 SpringMVC快速入门3 SpringMVC处理请求 3.1 请求分类及处理方式 3.1.1 静态请求3.1.2 动态请求 3.2 处理静态请求 3.2.1 处理html文件请求3.2.2 处理图片等请求 3.3 处理动态请求 3.3.1 注解说明3.3.2 示例 3.4 常见问题…

【用deepseek和chatgpt做算法竞赛】——还得DeepSeek来 -Minimum Cost Trees_5

往期 【用deepseek和chatgpt做算法竞赛】——华为算法精英实战营第十九期-Minimum Cost Trees_0&#xff1a;介绍了题目和背景【用deepseek和chatgpt做算法竞赛】——华为算法精英实战营第十九期-Minimum Cost Trees_1&#xff1a;题目输入的格式说明&#xff0c;选择了邻接表…

面试题汇总

1. 判断大小端问题 大端&#xff1a;低字节存放在高地址&#xff1b; 小端&#xff1a;低字节存放在低地址 如 : 0x12345678 bool is_little_endian() {unsigned int x 1;return ((char*)&x)[0]; }bool is_big_endian() {unsigned int x 1;return !((char*)&x)[0];…

jsherp importItemExcel接口存在SQL注入

一、漏洞简介 很多人说管伊佳ERP&#xff08;原名&#xff1a;华夏ERP&#xff0c;英文名&#xff1a;jshERP&#xff09;是目前人气领先的国产ERP系统虽然目前只有进销存财务生产的功能&#xff0c;但后面将会推出ERP的全部功能&#xff0c;有兴趣请帮点一下 二、漏洞影响 …

体验用ai做了个python小游戏

体验用ai做了个python小游戏 写在前面使用的工具2.增加功能1.要求增加视频作为背景。2.我让增加了一个欢迎页面。3.我发现中文显示有问题。4.我提出了背景修改意见&#xff0c;欢迎页面和结束页面背景是视频&#xff0c;游戏页面背景是静态图片。5.提出增加更多游戏元素。 总结…

动态存储斐波那契数列(递归优化)

递归 递归是c当中一种自身调用自身的算法。 普通递归解决斐波那契数列问题 #include<iostream> using namespace std; int f(int n){int sum;if(n<2){sum1;}else{sumf(n-1)f(n-2);}return sum; } int main() {int n;cin>>n;cout<<f(n);return 0;}当数据…

php文件上传

文章目录 文件上传机制文件上传脚本文件上传绕过php后缀替换为空web服务器的解析漏洞绕过nginxiisapache 高级文件上传nginx自定义配置文件&#xff08;默认三分钟刷新一次&#xff09;服务端内容检测结合伪协议使用配合日志包含只允许图片上传 上传实战训练 文件上传机制 文件…

播放器系列1——总概述

播放器核心架构 模块解释 文件读取 读取视频文件、读取网络文件、读取音频文件&#xff0c;大概分为这三种&#xff0c;目前代码中仅实现了读取视频文件播放&#xff0c;也就是当没有video数据的时候播放器不可使用。 解复用 容器指的是多媒体文件中的封装格式&#xff0c;…

【存储中间件API】MySQL、Redis、MongoDB、ES常见api操作及性能比较

常见中间件api操作及性能比较 ☝️ MySQL crud操作✌️ maven依赖✌️ 配置✌️ 定义实体类✌️ 常用api ☝️ Redis crud操作✌️ maven依赖✌️ 配置✌️ 常用api ☝️ MongoDB crud操作✌️ maven依赖✌️ 配置文件✌️ 定义实体类✌️ MongoDB常用api ☝️ ES crud操作 ⭐️…

【进程与线程】Linux 线程、同步以及互斥

每个用户进程有自己的地址空间。 线程是操作系统与多线程编程的基础知识。 系统为每个用户进程创建一个 task_struct 来描述该进程&#xff1a;该结构体中包含了一个指针指向该进程的虚拟地址空间映射表&#xff1a; 实际上 task_struct 和地址空间映射表一起用来表示一个进程…

实现动态翻转时钟效果的 HTML、CSS 和 JavaScript,附源码

实现动态翻转时钟效果的 HTML、CSS 和 JavaScript 在现代网页设计中&#xff0c;动画效果可以极大地增强用户体验。本文将介绍如何利用 HTML、CSS 和 JavaScript 创建一个动态翻转时钟的效果&#xff0c;模拟经典机械翻页时钟的视觉效果。我们将通过详细的步骤讲解如何实现时钟…