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

摘要

本文详细介绍了单例设计模式,包括其定义、结构、实现方法及适用场景。单例模式是一种创建型设计模式,确保一个类只有一个实例并提供全局访问点。其要点包括唯一性、私有构造函数、全局访问点和线程安全。文章还展示了单例设计模式的类图和时序图,并介绍了三种实现方式:饿汉式、静态内部类和枚举方式。最后列举了单例模式适合和不适合的场景,以及实战建议和示例,如配置中心、统一 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.pswp.cn/pingmian/83152.shtml

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

相关文章

Lyra学习笔记 Experience流程梳理

目录 前言1 创建2 加载3 Deactivate4 总结与图示 前言 这篇主要将视角放在Experience的流程&#xff0c;所以不会涉及一些更深的东西 之后ULyraExperienceManagerComponent简称为EMC 1 创建 完事开头难&#xff0c;首先找到了管理Experience的组件&#xff0c;那么它的初始化…

Ubuntu下编译mininim游戏全攻略

目录 一、安装mininim 软件所依赖的库&#xff08;重点是allegro游戏引擎库&#xff09;二、编译mininim 软件三、将mininim打包给另一个Ubuntu系统使用四、安卓手机运行mininim 一、安装mininim 软件所依赖的库&#xff08;重点是allegro游戏引擎库&#xff09; 1. 用apt-get…

SMT贴片制造流程关键环节解析

内容概要 现代电子制造领域中&#xff0c;SMT&#xff08;表面贴装技术&#xff09;作为核心工艺&#xff0c;其流程的精密性与稳定性直接决定产品性能与生产良率。本文以SMT贴片制造流程为主线&#xff0c;系统解析焊膏印刷、元器件贴装、回流焊接三大核心工艺的技术要点。其…

HTTP/2与HTTP/3特性详解:为你的Nginx/Apache服务器开启下一代Web协议

更多服务器知识&#xff0c;尽在hostol.com 嘿&#xff0c;各位站长和服务器管理员朋友们&#xff01;咱们天天跟网站打交道&#xff0c;都希望自己的网站能像火箭一样快&#xff0c;用户体验“嗖嗖”的。但你知道吗&#xff1f;除了服务器硬件配置、代码优化、CDN加速这些“常…

pytest 常见问题解答 (FAQ)

pytest 常见问题解答 (FAQ) 1. 基础问题 Q1: 如何让 pytest 发现我的测试文件&#xff1f; 测试文件命名需符合 test_*.py 或 *_test.py 模式测试函数/方法需以 test_ 开头测试类需以 Test 开头(且不能有__init__方法) Q2: 如何运行特定测试&#xff1f; pytest path/to/t…

【前端】SPA v.s. MPA

链接&#xff1a;页面结构 误区 页面结构管理有两种常见方式&#xff1a;路由形式 和 组件形式。路由形式 对应MPA &#xff0c;组件形式对应SPA ❌ 误区 1&#xff1a;路由形式 MPA❌ 路由是 SPA 和 MPA 共有的概念&#xff0c;区别在于路由映射的对象&#xff1a; MPA 的…

Matlab数据类型

本篇介绍我在南农matlab课程上的所学&#xff0c;我对老师ppt上的内容重新进行了整理并且给出代码案例。主要内容在矩阵。如果真的想学matlab&#xff0c;我不认为有任何文档能够超过官方文档&#xff0c;请移步至官网&#xff0c;本篇说实话只是写出来给自己和学弟学妹作期末复…

代码随想录算法训练营 Day58 图论Ⅷ 拓扑排序 Dijkstra

图论 题目 117. 软件构建 拓扑排序&#xff1a;给出一个有向图&#xff0c;把这个有向图转成线性的排序就叫拓扑排序。 当然拓扑排序也要检测这个有向图是否有环&#xff0c;即存在循环依赖的情况&#xff0c;因为这种情况是不能做线性排序的。所以拓扑排序也是图论中判断有向…

vscode中launch.json、tasks.json的作用及实例

文章目录 launch.json是什么作用多环境调试简单实例进阶使用核心配置项解析调试第三方程序 launch.json是什么 顾名思义&#xff1a;它是在.vscode文件夹下的launch.json&#xff0c;所以是vscode启动调试的配置文件。总结&#xff1a;通过定义调试参数、环境变量和启动方式&a…

NeRF PyTorch 源码解读 - 体渲染

文章目录 1. 体渲染公式推导1.1. T ( t ) T(t) T(t) 的推导1.2. C ( r ) C(r) C(r) 的推导 2. 体渲染公式离散化3. 代码解读 1. 体渲染公式推导 如下图所示&#xff0c;渲染图像上点 P P P 的颜色值 c c c 是累加射线 O P → \overrightarrow{OP} OP 在近平面和远平面范围…

标题:2025海外短剧爆发年:APP+H5双端系统开发,解锁全球流量与变现新大陆

描述&#xff1a; 2025年出海新风口&#xff01;深度解析海外短剧系统开发核心&#xff08;APPH5双端&#xff09;&#xff0c;揭秘高效开发策略与商业化路径&#xff0c;助您抢占万亿美元市场&#xff01; 全球娱乐消费模式正在剧变。2025年&#xff0c;海外短剧市场已从蓝海…

React JSX语法介绍(JS XML)(一种JS语法扩展,允许在JS代码中编写类似HTML的标记语言)Babel编译

在线调试网站&#xff1a;https://zh-hans.react.dev/learn 文章目录 JSX&#xff1a;现代前端开发的声明式语法概述JSX的本质与工作原理什么是JSXJSX转换流程 JSX语法特性表达式嵌入&#xff08;JSX允许在大括号内嵌入任何有效的JavaScript表达式&#xff09;属性传递&#xf…

Unity UI系统中RectTransform详解

一、基础代码示例 public GameObject node; var rect node.GetComponent<RectTransform>();Debug.Log($"anchoredPosition----{rect.anchoredPosition}"); Debug.Log($"offsetMin.x--{rect.offsetMin}"); Debug.Log($"offsetMax.x--{rect.of…

【数据库】并发控制

并发控制 在数据库系统&#xff0c;经常需要多个用户同时使用。同一时间并发的事务可达数百个&#xff0c;这就是并发引入的必要性。 常见的并发系统有三种&#xff1a; 串行事务执行&#xff08;X&#xff09;&#xff0c;每个时刻只有一个事务运行&#xff0c;不能充分利用…

我们来学mysql -- “数据备份还原”sh脚本

数据备份&还原 说明执行db_backup_cover.sh脚本 说明 环境准备&#xff1a;来源数据库(服务器A)&#xff1b;目标数据库(服务器B)dbInfo.sh脚本记录基本信息 来源库、目标库的ip、port及执行路径 # MySQL 客户端和 mysqldump 的路径 MYSQL_CLIENT"/work/oracle/mysql…

【NLP 78、手搓Transformer模型结构】

你以为走不出的淤泥&#xff0c;也迟早会云淡风轻 —— 25.5.31 引言 ——《Attention is all you need》 《Attention is all you need》这篇论文可以说是自然语言处理领域的一座里程碑&#xff0c;它提出的 Transformer 结构带来了一场技术革命。 研究背景与目标 在 Transfo…

深入理解CSS常规流布局

引言 在网页设计中&#xff0c;理解元素如何排列和相互作用至关重要。CSS提供了三种主要的布局方式&#xff1a;常规流、浮动和定位。本文将重点探讨最基础也是最常用的常规流布局&#xff08;Normal Flow&#xff09;&#xff0c;帮助开发者掌握页面布局的核心机制。 什么是…

树结构详细介绍(javascript版)

树结构的基本概念 树是一种非线性数据结构&#xff0c;由节点和连接节点的边组成。与线性数据结构&#xff08;如数组、链表&#xff09;不同&#xff0c;树具有层次结构&#xff0c;非常适合表示有层次关系的数据。 树的基本术语 节点 (Node)&#xff1a; 树中的基本单元&a…

element-plus bug整理

1.el-table嵌入el-image标签预览时&#xff0c;显示错乱 解决&#xff1a;添加preview-teleported属性 <el-table-column label"等级图标" align"center" prop"icon" min-width"80"><template #default"scope"&g…

RabbitMQ和MQTT区别与应用

RabbitMQ与MQTT深度解析&#xff1a;协议、代理、差异与应用场景 I. 引言 消息队列与物联网通信的重要性 在现代分布式系统和物联网&#xff08;IoT&#xff09;生态中&#xff0c;高效、可靠的通信机制是构建稳健、可扩展应用的核心。消息队列&#xff08;Message Queues&am…