当前位置: 首页 > news >正文

GoF设计模式——单例模式

前言

为什么需要单例模式?

想象一个场景:一个应用需要读取配置文件,如果每次读配置都 new 一个配置管理器出来,就会出现两个问题:

  1. 浪费资源:配置数据都一样,创建多个对象毫无意义
  2. 数据不一致:A 模块改了配置,B 模块的配置对象还是旧的

这时候需要的就是单例模式——保证一个类在整个应用中只有一个实例,并提供一个全局访问点。

一句话总结:有些东西,整个应用有一份就够了。

概念

单例的意思就是单个实例

单例模式是一种创建型设计模式, 它的核心思想是保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。

  • 只有一个实例:在整个应用程序中,只存在该类的一个实例对象,而不是创建多个相同类型的对象。
  • 全局访问点:为了让其他类能够获取到这个唯一实例,该类提供了一个全局访问点(通常是一个静态方法),通过这个方法就能获得实例。

实现

实现单例,必须满足三个条件:

  1. 构造函数私有化:禁止外部 new 对象
  2. 自己持有自己的实例:用一个静态变量保存
  3. 提供公开的获取方法:通过静态方法返回这个实例

饿汉式(最简单)

在类加载的时候就创建好实例,不管会不会被使用。

饿汉的意思是:等不及被调用,先把实例创建好。像一个饿汉一样,不管好不好吃,先填饱肚子再说。

public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

懒汉式

只有在请求实例时才会创建,如果在首次请求时还没有创建,就创建一个新的实例,如果已经创建,就返回已有的实例。

懒汉的意思是:被调用时才创建实例,不调用就不创建。像一个懒人一样,不催就不做。

public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

懒汉式+双重检查锁(最常用)

懒汉式写法会有一个问题——多线程下可能创建出多个实例。当有多个请求同时调用Singleton.getInstance()的时候,此时instance为空,则每个请求都可能new一个实例出来。

为了避免这种情况,可以通过加锁来确保只会有一个请求创建出实例:

public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {                    // 第一次检查,避免每次都加锁synchronized (Singleton.class) {if (instance == null) {            // 第二次检查,防止重复创建instance = new Singleton();}}}return instance;}
}

为什么要两次 if 检查?

  • 外层 if:实例已创建就直接返回,不用排队等锁,提高性能
  • 内层 if:多个线程同时过了外层检查,排队进入锁,第一个创建后,第二个进来发现已经有了就不再创建

扩展:为什么加 volatile
new Singleton() 这一行不是原子操作,实际分三步:

  1. 分配内存
  2. 初始化对象
  3. 引用指向内存
    JVM可能把顺序重排为1→3→2。线程A执行到第3步但还没初始化完,线程B进来发现 instance != null,直接返回了一个没初始化完的对象,程序就崩了。volatile 禁止这种重排序。

静态内部类(推荐)

结合了饿汉式和懒汉式的优点:线程安全 + 懒加载,而且没有锁的性能损耗。像把东西放在保险箱(内部类)里,不开箱东西就不会被拿出来。需要的时候一开箱,发现东西早就准备好了,而且只有一份。

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

原理SingletonHolder 这个内部类只有在调用 getInstance() 时才会被JVM加载,才会创建Singleton实例,而JVM保证类加载过程是线程安全的。所以既实现了懒加载,又保证了线程安全,还不用加锁。

枚举(最安全)

为什么枚举最安全?

  • 天然防止反射攻击(枚举不允许通过反射创建实例)
  • 天然支持序列化(反序列化不会产生新实例)
  • 代码最简洁,JVM保证唯一性

像身份证号,天生就唯一,不需要额外做任何保证。

public enum Singleton {INSTANCE;public void doSomething() {// 业务方法}
}// 使用
Singleton.INSTANCE.doSomething();

总结

优缺点

说明
优点
节省资源 全局只有一个实例,避免重复创建和内存浪费
数据一致 多处代码共享同一份状态,不会出现数据不同步的问题
全局访问 提供统一的访问入口,使用方便
缺点
难以测试 全局状态会污染测试环境,单测时需要特殊处理(如重置实例)
隐藏依赖 调用方通过 getInstance() 直接获取,依赖关系不透明,违反依赖注入原则
并发风险 单例的成员变量被多线程修改时,需要额外保证线程安全

实现方式对比

实现方式 线程安全 懒加载 防反射 防序列化 优点 缺点
饿汉式 实现最简单,没有锁开销 类加载就创建,浪费资源(如果用不到)
懒汉式+双重检查锁 懒加载,资源利用率高 写法复杂,需要 volatile,有锁开销
静态内部类 懒加载 + 无锁,写法简洁 无法传参给构造函数
枚举 最安全,天然防反射和序列化 不能继承其他类,写法不够直观

怎么选

  • 日常开发 → 静态内部类(简单、安全、懒加载)
  • 需要序列化 → 枚举(最安全)
  • Spring 项目 → 直接用 @Service,框架管理单例

简单记忆:不确定就用静态内部类,它是综合最优的选择。

练习题目

银行叫号系统

题目描述:某银行大厅有一台叫号机,两个窗口共用这台叫号机为客户取号。请用单例模式设计叫号机,确保所有窗口取到的号码连续且不重复。

输入描述:第一行输入一个整数n,表示客户数量。接下来n行,每行包含窗口编号(1或2)和客户姓名,用空格隔开。

输出描述:为每个客户分配排队号码,输出格式为"客户姓名 号码"。

输入示例

5
1 张三
2 李四
1 王五
2 赵六
1 钱七

输出示例

张三 1
李四 2
王五 3
赵六 4
钱七 5

解题思路:两个窗口各自获取叫号机实例,但必须是同一个实例——否则各拿各的计数器,号码就会重复。这就是单例模式解决的真正问题:多处代码需要共享同一个状态时,确保只有一个实例存在。

import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = Integer.parseInt(sc.nextLine());// 两个窗口各自获取叫号机TicketMachine window1 = TicketMachine.getInstance();TicketMachine window2 = TicketMachine.getInstance();for (int i = 0; i < n; i++) {String[] parts = sc.nextLine().split(" ");int window = Integer.parseInt(parts[0]);String name = parts[1];int number;if (window == 1) {number = window1.getNextNumber();} else {number = window2.getNextNumber();}System.out.println(name + " " + number);}}
}class TicketMachine {private int currentNumber;private TicketMachine() {currentNumber = 0;}private static class Holder {private static final TicketMachine INSTANCE = new TicketMachine();}public static TicketMachine getInstance() {return Holder.INSTANCE;}public int getNextNumber() {return ++currentNumber;}
}

扩展:实际项目中的单例

学完了实现方式,更重要的是知道在真实项目中什么时候会用到。以下场景大概率已经接触过,只是没意识到它们是单例。

Spring中的Bean(最常见)

Spring容器中的Bean默认就是单例@Service@Component 等注解标记的类,默认 scope = "singleton")。整个应用只有一个实例,所有请求共享,所以Spring应用处理请求很快——不用每次都创建新对象。

注意:单例Bean如果有成员变量被多线程修改,需要用 ThreadLocal 或加锁保证线程安全。

数据库连接池

数据库连接创建很慢(TCP握手、认证、分配资源),所以项目一定会用连接池(如 HikariCP、Druid),而连接池本身就是单例——整个应用只维护一个池子,所有模块共用。

本地缓存管理器

高频查询的数据(如字典表、省市区、热门商品)缓存在本地,减少数据库压力。缓存管理器通常用单例,保证整个应用共享一份缓存,避免内存浪费。

线程池管理器

线程池创建成本高,整个应用通常只维护几个线程池。通过单例统一管理,异步发邮件、推送消息、导出报表等耗时操作都通过同一个线程池执行,避免每个模块各自创建线程池导致资源浪费。

配置中心客户端

微服务中从 Nacos、Apollo 等配置中心拉取配置,这个客户端通常用单例——整个应用只需要一个连接来拉取和监听配置变更。

日志管理器

每天用的 LoggerFactory.getLogger() 背后也是单例。日志框架的核心组件(LoggerContext)是单例的,所有 Logger 共享同一个输出器,日志级别、格式等配置全局统一。

本文由mdnice多平台发布

http://www.jsqmd.com/news/775999/

相关文章:

  • 终极KaTeX性能优化指南:10个生产环境部署技巧让数学渲染速度提升300%
  • Windows右键菜单3步终极清理指南:告别杂乱,提升工作效率
  • 5月7日笔记
  • 终极指南:Handlebars.js循环渲染如何实现列表数据的多样化展示
  • 2026 徐州大克重黄金上门回收:福正美双人作业,全程录像备查 - 福正美黄金回收
  • 一文搞懂KMP算法(图解)
  • 2026年深圳纯直营驾培与智驾陪驾完全指南:宝华驾校如何破局行业乱象 - 优质企业观察收录
  • BitNet b1.58-2B-4T-gguf保姆级教学:WebUI中Max New Tokens与上下文截断关系详解
  • 新手避坑指南:用Colab T4 GPU复现STGCN交通预测模型(附完整代码)
  • Thorium浏览器:编译优化驱动的Chromium极致性能实现
  • 如何选择靠谱的天津汽车城?天津滨海国际汽车城给出答案 - 资讯焦点
  • 模型瘦身实战:用Torch-Pruning的Magnitude/BNScale策略,5步迭代剪枝你的PyTorch模型
  • 2026年深圳直营驾校与智驾陪驾完全避坑指南:宝华驾校如何打破行业乱象 - 优质企业观察收录
  • 抖音无水印下载终极指南:douyin-downloader完整使用教程
  • 别再迷信BBR了!用tc的4-state markov模型和iperf3,实测告诉你真实网络下的表现
  • 升学领航,筑梦全球——广州诺德安达学校招生启幕,以亮眼成果铺就成长坦途 - 资讯焦点
  • TargetMol疾病造模——Cisplatin(Cat. No. T1564, CAS. 15663-27-1):调控损伤、铁死亡与自噬 - 陶术生物
  • STK新手必看:从零开始,5分钟搞定第一个地面站和卫星场景
  • 深度学习笔记:从入门到核心概念
  • 从HelloWorld到GoodNight:手把手教你用OllyDBG修改PE文件字符串(附FOA/VA/RVA换算)
  • 挤馅机源头厂家:产品竞争力提升与市场拓展策略深度解析
  • 2026四川粘钢加固服务商优选:5 家正规靠谱企业,专业做房屋结构加固 - 深度智识库
  • Hunyuan-MT-7B内容出海应用:自媒体一键生成英/日/韩/法/西多语版本
  • Windows鼠标指针方案一键切换:原理、工具与自定义指南
  • 拨开“分子递送迷雾”——百代生物以底层创新重塑核酸与蛋白质转染试剂版图 - 资讯焦点
  • 告别Adobe Acrobat!用Aspose.PDF for .NET 23.1.0实现PDF文档的自动化处理(附代码示例)
  • TranslucentTB终极指南:3步解决任务栏透明美化启动失败问题
  • 2026年陕西画册印刷厂、图文快印代工与不干胶标签印刷全景指南 - 精选优质企业推荐官
  • CTF密码学实战:当RSA公钥e过大时,如何用Boneh-Durfee攻击还原DASCTF的so-large-e题目
  • 大人吃的鱼油什么牌子好?2026知名鱼油品牌推荐:心脑养护效果科学温和超明显 - 资讯焦点