一、为什么需要单例模式?

在软件系统中,有些对象我们只希望存在 一个实例。
比如:

  • 系统配置类(Configuration Manager)
  • 日志管理类(Logger)
  • 数据库连接池(Connection Pool)
  • 线程池(Thread Pool)
  • 缓存管理类(CacheManager)

这些对象如果被频繁创建,不仅浪费资源,还可能造成状态不一致。
于是 —— 单例模式(Singleton Pattern) 应运而生。


二、定义与核心思想

定义:
确保一个类只有一个实例,并提供一个全局访问点来获取它。

核心思想:

  • 限制外部创建对象的能力
  • 提供统一的获取方式
  • 确保线程安全(在多线程环境下尤为重要)

三、单例模式 UML 图

📌单例模式结构图

Singleton
- static Singleton instance
-Singleton()
+static Singleton getInstance()

解释:

  • 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 实现原理)

否
是
是
否
线程访问 getInstance
instance == null?
返回实例
进入同步块
instance == null?
创建实例

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<String, Object> cache = new HashMap<>();

    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✅✅⭐⭐⭐⭐✅ 推荐
静态内部类✅✅⭐⭐⭐⭐⭐✅ 强烈推荐
枚举单例✅✅⭐⭐⭐⭐⭐✅ 终极方案

八、什么时候该使用单例模式?🤔

适用情况判断标准示例
需要全局唯一实例系统只需要一个对象协调操作日志管理、配置中心
对象创建成本高频繁创建对象浪费性能数据库连接池、线程池
需要全局共享状态多模块需共享同一数据缓存、配置加载器
控制资源访问需控制资源总量文件句柄、网络连接

⚠️ 不推荐使用的情况:

  • 对象确实需要多个独立实例(如多用户会话)
  • 会引入全局状态导致难以测试(可考虑依赖注入代替)

九、总结 🌟

要点说明
目的确保类只有一个实例
实现核心私有构造 + 静态实例 + 全局访问方法
线程安全推荐静态内部类、枚举
常用场景日志系统、配置中心、连接池、缓存、线程池
注意防止反射与反序列化破坏单例

✨ 结语

单例模式是所有设计模式中最基础、使用最广的之一。
它不仅仅是“一个实例”,更是一种对 资源管理与全局状态控制 的思考。
掌握好它,是通向更高层架构设计的第一步。

更多推荐