设计模式——单例设计模式(创建型)

article/2025/6/16 6:16:18

摘要

本文详细介绍了单例设计模式,包括其定义、结构、实现方法及适用场景。单例模式是一种创建型设计模式,确保一个类只有一个实例并提供全局访问点。其要点包括唯一性、私有构造函数、全局访问点和线程安全。文章还展示了单例设计模式的类图和时序图,并介绍了三种实现方式:饿汉式、静态内部类和枚举方式。最后列举了单例模式适合和不适合的场景,以及实战建议和示例,如配置中心、统一 ID 生成器、日志收集器等。

1. 单例设计模式定义

单例模式是一种创建型设计模式,其目的是确保一个类只有一个实例,并提供一个全局访问点来获取该实例

通俗理解:

  • 单例模式就是让一个类只创建一个对象,就像系统中只能有一个“总统”或“日志管理器”。
  • 这个类自己控制这个唯一实例的创建,并且其他类只能通过它提供的方法来获取这个对象。

要点

说明

唯一性

类只能有一个实例

私有构造函数

禁止外部直接用 new创建对象

全局访问点

提供一个静态方法获取该实例

线程安全(可选)

在多线程环境下仍能保持唯一性

2. 单例设计模式结构

2.1. 单例设计模式类图

2.2. 单例设计模式时序图

3. 单例设计模式实现方式

所有单例的实现都包含以下两个相同的步骤:

  1. 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  2. 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

实现方式

是否线程安全

是否懒加载

推荐程度

饿汉式

✅ 推荐(简单可靠)

懒汉式(线程不安全)

❌ 不推荐

懒汉式 + synchronized

⚠️ 有性能开销

双重检查锁(DCL)

✅ 推荐(兼顾性能)

静态内部类

✅ 推荐(懒加载 + 安全)

枚举方式

✅ 最推荐(防反射、反序列化)

3.1. 📍 饿汉式(推荐)

public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {} // 构造器私有化public static Singleton getInstance() {return instance;}
}

3.2. 📍 静态内部类(推荐)

public class Singleton {private Singleton() {}private static class Holder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return Holder.INSTANCE;}
}

3.3. 📍 枚举方式(最安全)

public enum Singleton {INSTANCE;public void doSomething() {System.out.println("do...");}
}

4. 单例设计模式适合场景

4.1. ✅ 单例模式适合的场景

场景类别

说明

示例

配置管理类

系统中读取一次后多处使用,需全局共享

AppConfig.getInstance().get("db.url")

日志系统

全局统一记录日志,防止多个文件或实例导致管理混乱

Logger.getInstance().log("...")

线程池 / 连接池

统一管理资源,避免重复创建、浪费连接

DbConnectionPool.getInstance().getConnection()

任务调度器

控制任务执行的唯一调度入口

TaskScheduler.getInstance().schedule(task)

唯一 ID 生成器

全局 ID 要求唯一,需中心化生成

IdGenerator.getInstance().nextId()

系统监控模块

全局收集监控信息,避免多个统计点造成数据不一致

MetricsCollector.getInstance().record("qps", 5)

4.2. ❌ 单例模式不适合的场景

场景类别

问题描述

示例或说明

会话/用户状态类

多用户或请求需独立状态,单例可能造成状态串扰或数据混乱。

用户登录状态、购物车信息等

多实例业务模型

业务本身设计要求一个类存在多个不同实例

订单、交易、商品等

单元测试场景

单例难以隔离状态,不利于并发测试和 mock。

单例残留状态会污染其他测试

生命周期绑定业务对象

对象需按请求或事务创建销毁,单例不符合生命周期需求。

HTTP 请求上下文、数据库事务上下文

状态频繁变化类

状态共享会导致线程不安全,需加锁处理复杂性上升。

非线程安全的缓存组件、计算任务执行状态

需依赖注入管理的类

单例可能和 Spring 等框架的容器管理冲突,影响可测试性和解耦性。

建议用 Spring Bean 单例管理(@Component + @Scope

4.3. 🧠 单例设计模式实战建议

使用场景

是否推荐使用单例

说明

配置类 / 常量类

✅ 是

全局唯一即可

Controller/Service

❌ 否

由 Spring 容器管理生命周期更合适

每个用户/请求有状态

❌ 否

应使用原型模式或线程隔离

工具类(无状态)

⚠️ 视情况

可以用 static 工具类替代

5. 单例设计模式实战示例

在 Spring 项目中,单例模式(Singleton Pattern)使用场景非常广泛。Spring 容器管理的 Bean 默认就是单例模式,它本质上满足了单例设计模式的定义:“确保一个类只有一个实例,并且提供一个全局访问点。所以在 Spring 项目中,我们一般直接使用 Spring 单例 Bean,既符合单例设计模式的定义,又简化了开发和维护的复杂度。下面是一些常见 适合使用单例的场景 及其在 Spring 中的实现示例:

设计模式核心要求

Spring Bean 示例体现方式

唯一实例(Singleton)

Spring 容器中该 Bean 默认只实例化一次,所有注入该 Bean 的地方都共享同一个实例。

全局访问点

通过 Spring 的依赖注入(@Autowired)或获取 Bean 的方式,全局访问同一个实例。

控制实例创建(防止多次 new)

不用 new 直接调用构造器,而是由 Spring 容器负责实例创建和生命周期管理。

线程安全(视具体实现而定)

需要保证成员变量线程安全,比如用线程安全的数据结构或无状态设计。

5.1. ✅ 配置中心 / 配置管理器

使用场景: 需要在系统中读取一次配置,供全局使用。

@Component
@Data
public class AppConfig {@Value("${app.env}")private String env;
}

使用方式:

@Service
public class MyService {@Autowiredprivate AppConfig appConfig;public void doSomething() {System.out.println(appConfig.getEnv());}
}

5.2. ✅ 统一 ID 生成器(如雪花算法)

@Component
public class IdGenerator {private final AtomicLong counter = new AtomicLong();public long nextId() {return counter.incrementAndGet();}
}

使用方式:

@Service
public class OrderService {@Autowiredprivate IdGenerator idGenerator;public void createOrder() {Long orderId = idGenerator.nextId();// 创建订单逻辑}
}

5.3. ✅ 日志收集器 / 监控埋点上报器

@Component
public class MetricsCollector {public void record(String metric, int value) {// 上报指标逻辑System.out.println("metric: " + metric + " value: " + value);}
}

使用方式:

@Service
public class PaymentService {@Autowiredprivate MetricsCollector metricsCollector;public void pay() {// 业务逻辑metricsCollector.record("payment.count", 1);}
}

5.4. ✅ 缓存组件(轻量场景)

@Component
public class LocalCache {private final Map<String, Object> cache = new ConcurrentHashMap<>();public void put(String key, Object value) {cache.put(key, value);}public Object get(String key) {return cache.get(key);}
}

5.5. ✅ 线程池 / 异步任务执行器(通过 Spring 管理)

@Configuration
public class ThreadPoolConfig {@Beanpublic Executor taskExecutor() {return Executors.newFixedThreadPool(10);}
}

使用方式:

@Service
public class AsyncTaskService {@Autowiredprivate Executor taskExecutor;public void runAsyncTask() {taskExecutor.execute(() -> System.out.println("Running async task"));}
}

5.6. ✅ 策略工厂 / 状态机容器

这些模式本质上也是通过单例注册机制实现的,通常用 @Component + Map<String, Strategy> 组合来做策略路由。

@Component
public class StrategyFactory {private final Map<String, Strategy> strategies;public StrategyFactory(List<Strategy> strategyList) {strategies = new HashMap<>();for (Strategy s : strategyList) {strategies.put(s.getType(), s);}}public Strategy get(String type) {return strategies.get(type);}
}

5.7. ✅ Spring 项目中适合单例的场景

场景名称

Spring 推荐实现

是否线程安全

配置类

@Component+ @Value

✅ 是

ID 生成器

@Component+ 原子类

✅ 是

日志/监控工具

@Component+ 线程安全方法

✅ 是

缓存组件

@Component+ ConcurrentMap

✅ 是(注意并发)

工具类

@Componentstatic工具类

⚠️ 视情况

6. 单例设计模式思考

6.1. 为什么spring中对象天然是单例?

6.1.1. Spring 容器设计初衷

  • Spring 是一个IoC(控制反转)容器,负责管理应用中的对象生命周期和依赖关系。
  • 容器初始化时,会根据配置(注解或 XML)创建并管理 Bean 实例。
  • 默认情况下,Spring 容器只会创建一个共享的 Bean 实例,供所有依赖该 Bean 的组件共享使用。

6.1.2. 单例 Bean 的定义和作用域

  • Spring 中的单例是指在 Spring 容器中只有一个实例,而不是 JVM 层面上的全局单例。
  • 默认作用域是 singleton,即:每个 Spring 容器中该 Bean 只有一个实例
  • 你可以通过 @Scope("prototype") 等其他作用域来改变默认行为。

6.1.3. Spring 单例实现机制(简要)

  • 容器启动时,会扫描并实例化所有单例 Bean。
  • 创建后,将实例放入一个单例缓存池(例如 singletonObjects)。
  • 当其他组件请求该 Bean 时,直接从缓存池取,避免重复创建。
  • 通过这种方式,Spring 确保每个 Bean 在容器内是唯一的。

6.1.4. 为什么默认使用单例?

  • 节省资源:不必每次调用都创建新实例,减少内存开销。
  • 方便共享:多个组件可以共享状态或行为一致的对象。
  • 生命周期管理:由容器统一管理,便于统一销毁或初始化。
  • 线程安全的前提下,提高性能:一般单例 Bean 设计为无状态或线程安全,避免多次实例化开销。

6.1.5. 需要注意的点

  • Spring 的单例是“容器单例”,不同的 Spring 容器可以有不同的实例。
  • 如果使用多个容器或类加载器,则可能出现多实例。
  • 单例 Bean 设计时应注意线程安全,避免可变状态带来的并发问题。
  • 业务中有状态的 Bean 一般不要用单例,使用 prototype 或其他作用域。

6.2. Spring 的单例是“容器单例”,不同的 Spring 容器可以有不同的实例。

意思是: Spring 单例不是 JVM 层面全局的单例,而是“每个 Spring 容器(ApplicationContext)中唯一的实例”。如果你项目里启动了多个 Spring 容器(比如多个 ApplicationContext 实例),每个容器都会单独创建自己的那个 Bean 实例。

举例:

  1. 你有两个 Web 应用,每个运行一个 Spring 容器,它们各自有自己的单例 Bean 实例。
  2. 或者你启动了多个 Spring 容器做测试、隔离等,也会有多个实例。

6.3. 如果使用多个容器或类加载器,则可能出现多实例

类加载器(ClassLoader)不同,虽然类名相同,但被 JVM 认为是不同的类。因此,如果你在不同的类加载器中加载同一个类,也会导致出现“多个单例实例”,不是同一个对象。

典型场景:

  • Java EE 容器中不同的部署单元(war包、ear包)
  • 插件式架构、模块化系统
  • 热部署(热更新)时重载类

6.4. 单例Bean设计时应注意线程安全,避免可变状态带来的并发问题

Spring 单例Bean是被多个线程共享的(特别是 Web 应用中,多个请求同时访问)。如果单例 Bean 内部有可变的成员变量,就会有线程安全风险,可能导致数据错乱或异常。

设计原则:

  • 无状态设计: Bean 不保存业务状态,所有状态通过方法参数传递。
  • 线程安全的数据结构: 比如使用 ConcurrentHashMapAtomicInteger
  • 同步控制: 必要时用锁、synchronized 保证并发安全。

6.5. 业务中有状态的 Bean 一般不要用单例,使用 prototype 或其他作用域

有状态 Bean:保存用户会话、操作数据等状态的 Bean。用单例的话,状态被多个线程共享,会导致状态混乱和并发问题。这时应使用 Spring 的其他作用域,比如:

  • prototype:每次请求都会创建新实例,避免共享状态。
  • request(Web作用域):每个 HTTP 请求一个实例。
  • session:每个用户会话一个实例。

博文参考

  • 5. 单例模式 — Graphic Design Patterns
  • 单例设计模式
  • 创建型 - 单例模式(Singleton pattern) | Java 全栈知识体系

http://www.hkcw.cn/article/PJIBhZpTuP.shtml

相关文章

STM32F103C8T6,bxCAN收发配置实例,包含ID过滤

文章目录 引言bxCAN简介bxCAN主要特点代码示例引言 bxCAN简介 bxCAN是基本扩展CAN(Basic Extended CAN)的缩写,它支持CAN协议2.0A和2.0B。它的设计目标是,以最小的CPU负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。 对于安全紧要的应…

使用 HTML + JavaScript 实现可拖拽的任务看板系统

本文将介绍如何使用 HTML、CSS 和 JavaScript 创建一个交互式任务看板系统。该系统支持拖拽任务、添加新任务以及动态创建列&#xff0c;适用于任务管理和团队协作场景。 效果演示 页面结构 HTML 部分主要包含三个默认的任务列&#xff08;待办、进行中、已完成&#xff09;和…

进程间通信II·命名管道

目录 原理 创建过程 特性 代码练习 客户端与服务端交互 小知识 原理 原理&#xff1a;两个进程各自的struct file 指向相同的inode和文件缓冲区&#xff08;这里的inode和文件缓冲区也应用了引用计数&#xff09;。 命名管道创建的是磁盘上的一种不刷新数据到缓冲区的常规…

Redis--缓存工具封装

经过前面的学习&#xff0c;发现缓存中的问题&#xff0c;无论是缓存穿透&#xff0c;缓存雪崩&#xff0c;还是缓存击穿&#xff0c;这些问题的解决方案业务代码逻辑都很复杂&#xff0c;我们也不应该每次都来重写这些逻辑&#xff0c;我们可以将其封装成工具。而在封装的时候…

ZC-OFDM雷达通信一体化减小PAPR——选择性映射法(SLM)

文章目录 前言一、SLM 技术1、简介2、原理 二、MATLAB 仿真1、核心代码2、仿真结果 三、资源自取 前言 在 OFDM 雷达通信一体化系统中&#xff0c;信号的传输由多个子载波协同完成&#xff0c;多个载波信号相互叠加形成最终的发射信号。此叠加过程可能导致信号峰值显著高于其均…

ESP32-idf学习(四)esp32C3驱动lcd

一、前言 屏幕是人机交互的重要媒介&#xff0c;而且现在我们产品升级的趋势越来越高大尚&#xff0c;不少产品都会用lcd来做界面&#xff0c;而esp32c3在一些项目上是可以替代主mcu&#xff0c;所以驱动lcd也是必须学会的啦 我新买的这块st7789&#xff0c;突然发现是带触摸…

Remote Sensing投稿记录(投稿邮箱写错、申请大修延期...)风雨波折投稿路

历时近一个半月&#xff0c;我中啦&#xff01; RS是中科院二区&#xff0c;2023-2024影响因子4.2&#xff0c;五年影响因子4.9。 投稿前特意查了下预警&#xff0c;发现近五年都不在预警名单中&#xff0c;甚至最新中科院SCI分区&#xff08;2025年3月&#xff09;在各小类上…

ZC-OFDM雷达通信一体化减小PAPR——部分传输序列法(PTS)

文章目录 前言一、PTS 技术1、简介2、原理 二、MATLAB 仿真1、核心代码2、仿真结果 三、资源自取 前言 在 OFDM 雷达通信一体化系统中&#xff0c;信号的传输由多个子载波协同完成&#xff0c;多个载波信号相互叠加形成最终的发射信号。此叠加过程可能导致信号峰值显著高于其均…

第6章 放大电路的反馈

本章基本要求 会判&#xff1a;判断电路中有无反馈及反馈的性质 会算&#xff1a;估算深度负反馈条件下的放大倍数 会引&#xff1a;根据需求引入合适的反馈 会判振消振&#xff1a;判断电路是否能稳定工作&#xff0c;会消除自激振荡。 6.1 反馈的概念及判断 一、反馈的…

知识管理五强对比:Baklib高效突围

Baklib核心技术优势 Baklib的底层技术架构以知识中台为核心&#xff0c;深度融合自然语言处理&#xff08;NLP&#xff09;与分布式存储技术&#xff0c;实现多源异构数据的统一纳管。其智能分类引擎通过语义理解自动关联碎片化文档&#xff0c;结合动态标签体系与多维度权限控…

电机驱动器辐射骚扰整改

定位低压DC部分的骚扰源&#xff08;排除法&#xff09;&#xff1a; 为确定是电源哪部分出现问题&#xff0c;可以采取如下步骤进行验证&#xff1a; a.将12V转5V的芯片去掉&#xff0c;仅剩12V器件工作&#xff0c;然后测试&#xff1b; b.将5V转3.3V和隔离5V的芯片去掉&am…

CTFHub-RCE 命令注入-过滤空格

观察源代码 代码里面可以发现过滤了空格 判断是Windows还是Linux 源代码中有 ping -c 4 说明是Linux 查看有哪些文件 127.0.0.1|ls 打开flag文件 我们尝试将空格转义打开这个文件 利用 ${IFS} 127.0.0.1|cat${IFS}flag_195671031713417.php 可是发现 文本内容显示不出来&…

2022年 中国商务年鉴(excel电子表格版)

2022年 中国商务年鉴&#xff08;excel电子表格版&#xff09;.ziphttps://download.csdn.net/download/2401_84585615/89772883 https://download.csdn.net/download/2401_84585615/89772883 《中国商务年鉴2022》是由商务部国际贸易经济合作研究院主办的年度统计资料&#xf…

家长速查!3岁男童误吞“水精灵”危及生命

给孩子挑选放心的玩具是不少家长群讨论的热点。“小玩具”关乎“大安全”,如何帮助孩子远离“毒”“危”玩具?怎样合理选购、安全使用,让玩具成为孩子的益友?“六一”国际儿童节前夕,记者就此进行了走访。“毒”“危”玩具有何隐患?“本月我们又接诊了一名3岁男童误吞‘水…

划龙舟有多拼 鼓点一响全员开挂 岭南文化盛宴

广东龙舟不仅是一种仪式,更是一种文化符号。每一声鼓点都充满了热血与奋进,每一次冲刺都体现了拼搏与荣光。“下水!起桨!”有着20多年“龙舟龄”的东莞万江街道龙舟划手黄柱良,为了近日在东江江面举行的龙舟趁景活动,和伙伴们准备了1个多星期。活动当天上午,黄柱良和其他…

大巴黎如何拿到2025年欧冠的 战术转型与团队足球

2025年6月1日凌晨,2024-2025赛季欧冠决赛在慕尼黑安联球场举行,巴黎圣日耳曼以5-0大胜国际米兰,队史首次夺得欧冠奖杯。这场胜利不仅终结了巴黎多年来的“欧冠魔咒”,也标志着球队在姆巴佩离队后的战术转型取得巨大成功。比赛期间,大巴黎主帅恩里克延续了本赛季后半段的43…

thinkpad T-440p 2025.05.31

thinkpad T-440p 2025.05.31 老了退休了&#xff0c;说起来真的可恶现在笔记本的设计师&#xff0c;只有固态硬盘了

堆与堆排序及 Top-K 问题解析:从原理到实践

一、堆的本质与核心特性 堆是一种基于完全二叉树的数据结构&#xff0c;其核心特性为父节点与子节点的数值关系&#xff0c;分为大堆和小堆两类&#xff1a; 大堆&#xff1a;每个父节点的值均大于或等于其子节点的值&#xff0c;堆顶元素为最大值。如: 小堆&#xff1a;每个…

【题解-洛谷】P8094 [USACO22JAN] Cow Frisbee S

题目&#xff1a;P8094 [USACO22JAN] Cow Frisbee S 题目描述 Farmer John 的 N ( N ≤ 3 10 5 ) N\ (N\le 3\times 10^5) N (N≤3105) 头奶牛的高度为 1 , 2 , … , N 1, 2, \ldots, N 1,2,…,N。一天&#xff0c;奶牛以某个顺序排成一行玩飞盘&#xff1b;令 h 1 … h …

如何利用差分隐私技术在医疗领域守护患者隐私

在数字化医疗快速发展的当下&#xff0c;医疗数据已然成为一座蕴藏无限价值的宝库。一份完整的电子病历&#xff0c;不仅记录着患者的疾病诊断、治疗记录&#xff0c;还可能包含基因数据、生活习惯等敏感信息&#xff1b;而基因检测报告中携带的遗传密码&#xff0c;更是与个人…