一、为什么需要单例模式?
在软件系统中,有些对象我们只希望存在 一个实例。
比如:
系统配置类(Configuration Manager)日志管理类(Logger)数据库连接池(Connection Pool)线程池(Thread Pool)缓存管理类(CacheManager)
这些对象如果被频繁创建,不仅浪费资源,还可能造成状态不一致。
于是 —— 单例模式(Singleton Pattern) 应运而生。
二、定义与核心思想
定义:
确保一个类只有一个实例,并提供一个全局访问点来获取它。
核心思想:
限制外部创建对象的能力提供统一的获取方式确保线程安全(在多线程环境下尤为重要)
三、单例模式 UML 图
📌单例模式结构图
解释:
Singleton 类包含一个 私有静态实例构造方法为 私有提供一个 静态方法 getInstance() 返回唯一实例
四、单例模式的常见实现方式
1️⃣ 饿汉式(Eager Initialization)
public class Singleton {
// 类加载时就创建对象
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
特点:
优点:实现简单,线程安全缺点:类加载时即创建实例,可能浪费内存
适用场景:
单例对象占用内存小,且加载即用的情况
2️⃣ 懒汉式(Lazy Initialization)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 非线程安全!
}
return instance;
}
}
问题:
在多线程下可能同时进入 if (instance == null),造成 多个实例。
解决办法: 加锁(但性能低)
3️⃣ 线程安全懒汉式(Synchronized 版本)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:
简单安全
缺点:
每次访问都需要锁,性能低下(特别是在高并发场景)
4️⃣ 双重检查锁(Double-Checked Locking, DCL)
✅ 推荐使用(兼顾性能与线程安全)
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;
}
}
关键点:
使用 volatile 保证内存可见性,防止指令重排两次 if (instance == null) 避免重复加锁
过程图:
📌 双重检查锁流程图(DCL 实现原理)
5️⃣ 静态内部类(推荐写法)
⭐ 最优实现之一 —— 线程安全、懒加载、实现简单
public class Singleton {
private Singleton() {}
// 静态内部类,只有在第一次调用 getInstance 时才会被加载
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
原理:
Holder 类不会在 Singleton 加载时立即初始化当调用 getInstance() 时,Holder 被加载并创建唯一实例由 JVM 保证线程安全
6️⃣ 枚举单例(Enum Singleton)
由 Java 官方推荐的终极方案!
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Enum Singleton works!");
}
}
优点:
天然防止反射攻击自动序列化支持实现极为简洁
使用方式:
Singleton.INSTANCE.doSomething();
五、常见问题与陷阱 ⚠️
问题原因解决方案多线程环境下创建多个实例懒汉式未加锁使用 DCL 或 静态内部类反射可破坏单例可通过反射调用私有构造器构造函数中添加防护反序列化生成新对象readResolve() 返回单例对象实现 readResolve() 方法protected Object readResolve() {
return getInstance();
}
六、单例模式在项目中的实际应用 🌐
在实际开发中,单例模式的核心价值是:
控制全局唯一资源的访问,避免重复实例化带来的性能与逻辑问题。
下面列举几个常见的使用场景👇
✅ 场景一:全局日志管理(Logger)
日志系统是最经典的单例模式应用。
为什么:
需要全局统一的输出入口(避免多线程同时写文件导致混乱)多个类都可能要记录日志,若每次都创建实例性能浪费
public class Logger {
private static final Logger INSTANCE = new Logger();
private Logger() {}
public static Logger getInstance() {
return INSTANCE;
}
public void log(String msg) {
System.out.println("[LOG] " + msg);
}
}
// 使用
Logger.getInstance().log("System initialized");
📘 使用场景:
Web 服务全局日志调试日志业务行为追踪
✅ 场景二:数据库连接池(Database Connection Pool)
数据库连接创建非常耗时。
系统中只需维护一个连接池对象,由它统一分配和回收连接。
public class DBConnectionPool {
private static final DBConnectionPool INSTANCE = new DBConnectionPool();
private DBConnectionPool() {
// 初始化连接池
}
public static DBConnectionPool getInstance() {
return INSTANCE;
}
public void execute(String sql) {
System.out.println("Executing SQL: " + sql);
}
}
// 使用
DBConnectionPool.getInstance().execute("SELECT * FROM users");
📘 使用场景:
JDBC 连接管理ORM 框架连接池(如 MyBatis、Hibernate)
✅ 场景三:系统配置中心(Configuration Manager)
整个系统通常只需要一个配置加载器。
从配置文件或环境变量读取后缓存到内存中,供全局使用。
public class ConfigManager {
private static volatile ConfigManager instance;
private Properties config;
private ConfigManager() {
config = new Properties();
config.setProperty("env", "production");
}
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
if (instance == null) instance = new ConfigManager();
}
}
return instance;
}
public String getConfig(String key) {
return config.getProperty(key);
}
}
// 使用
String env = ConfigManager.getInstance().getConfig("env");
📘 使用场景:
读取配置文件(application.yml / config.json)全局参数共享环境切换(dev、test、prod)
✅ 场景四:线程池管理器(ThreadPoolManager)
多线程程序中,线程的创建与销毁开销大。
单例线程池可以重复利用线程资源,提高性能。
import java.util.concurrent.*;
public class ThreadPoolManager {
private static final ThreadPoolManager INSTANCE = new ThreadPoolManager();
private final ExecutorService executor = Executors.newFixedThreadPool(5);
private ThreadPoolManager() {}
public static ThreadPoolManager getInstance() {
return INSTANCE;
}
public void submitTask(Runnable task) {
executor.submit(task);
}
}
// 使用
ThreadPoolManager.getInstance().submitTask(() -> {
System.out.println("Task running...");
});
📘 使用场景:
后台任务处理异步执行模块日志上传、消息推送、监控上报等
✅ 场景五:全局缓存(CacheManager)
在 Web 系统中,有些数据(如用户信息、字典表)经常被访问。
可以设计一个单例缓存类,减少数据库查询次数。
import java.util.*;
public class CacheManager {
private static final CacheManager INSTANCE = new CacheManager();
private final Map
private CacheManager() {}
public static CacheManager getInstance() {
return INSTANCE;
}
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
}
// 使用
CacheManager.getInstance().put("user:1001", "张三");
System.out.println(CacheManager.getInstance().get("user:1001"));
📘 使用场景:
用户信息缓存数据字典、配置缓存API 请求缓存
七、各版本性能对比
实现方式是否懒加载线程安全性能推荐程度饿汉式❌✅⭐⭐⭐⭐✅ 稳定懒汉式✅❌⭐⭐⭐⭐❌Synchronized 懒汉式✅✅⭐⭐⚠️ 性能差DCL✅✅⭐⭐⭐⭐✅ 推荐静态内部类✅✅⭐⭐⭐⭐⭐✅ 强烈推荐枚举单例✅✅⭐⭐⭐⭐⭐✅ 终极方案
八、什么时候该使用单例模式?🤔
适用情况判断标准示例需要全局唯一实例系统只需要一个对象协调操作日志管理、配置中心对象创建成本高频繁创建对象浪费性能数据库连接池、线程池需要全局共享状态多模块需共享同一数据缓存、配置加载器控制资源访问需控制资源总量文件句柄、网络连接
⚠️ 不推荐使用的情况:
对象确实需要多个独立实例(如多用户会话)会引入全局状态导致难以测试(可考虑依赖注入代替)
九、总结 🌟
要点说明目的确保类只有一个实例实现核心私有构造 + 静态实例 + 全局访问方法线程安全推荐静态内部类、枚举常用场景日志系统、配置中心、连接池、缓存、线程池注意防止反射与反序列化破坏单例✨ 结语
单例模式是所有设计模式中最基础、使用最广的之一。
它不仅仅是“一个实例”,更是一种对 资源管理与全局状态控制 的思考。
掌握好它,是通向更高层架构设计的第一步。