Skip to content
Method约 1 分钟0 个小节更新于 2026/06/19在线编辑

单例模式

单例模式用于保证一个类在整个 JVM 进程中只有一个实例,并提供一个全局访问点。

它适合管理全局唯一、创建成本高、需要共享状态或共享资源的对象,例如配置中心、线程池、连接池、日志组件等。

解决什么问题

如果一个对象被随意创建,可能出现这些问题:

  • 多个实例持有不同状态,导致数据不一致。
  • 对象创建成本高,频繁创建浪费资源。
  • 某些资源本来就应该唯一,例如全局配置、线程池。

单例模式把“实例创建权”收回到类内部,让外部只能通过固定入口拿对象。

基本结构

text
Singleton
  - private static instance
  - private constructor
  + public static getInstance()

关键点:

  • 构造方法私有化,禁止外部 new
  • 实例由类自己保存。
  • 提供静态方法获取实例。
  • 多线程环境下必须保证线程安全。

推荐实现:静态内部类

静态内部类是 Java 中比较推荐的写法,兼顾懒加载和线程安全。

java
public final class AppConfig {

    private AppConfig() {
    }

    private static class Holder {
        private static final AppConfig INSTANCE = new AppConfig();
    }

    public static AppConfig getInstance() {
        return Holder.INSTANCE;
    }
}

为什么线程安全:

  • 外部类加载时不会立即加载 Holder
  • 第一次调用 getInstance() 时才加载 Holder
  • 类加载过程由 JVM 保证线程安全。

枚举单例

如果需要最强的防御能力,枚举单例也很常用。

java
public enum IdGenerator {
    INSTANCE;

    public long nextId() {
        return System.currentTimeMillis();
    }
}

调用:

java
long id = IdGenerator.INSTANCE.nextId();

枚举单例天然防止反射和反序列化破坏单例,缺点是写法不一定符合所有团队习惯。

不推荐的懒汉式

下面这种写法在多线程下可能创建多个实例:

java
public class UnsafeSingleton {
    private static UnsafeSingleton instance;

    private UnsafeSingleton() {
    }

    public static UnsafeSingleton getInstance() {
        if (instance == null) {
            instance = new UnsafeSingleton();
        }
        return instance;
    }
}

如果两个线程同时进入 if (instance == null),就可能各自创建一个对象。

双重检查锁

双重检查锁可以用,但必须加 volatile

java
public class DatabaseClient {
    private static volatile DatabaseClient instance;

    private DatabaseClient() {
    }

    public static DatabaseClient getInstance() {
        if (instance == null) {
            synchronized (DatabaseClient.class) {
                if (instance == null) {
                    instance = new DatabaseClient();
                }
            }
        }
        return instance;
    }
}

volatile 的作用是避免指令重排,防止其他线程拿到一个还没初始化完成的对象。

使用场景

  • 全局配置读取器。
  • 应用级缓存管理器。
  • 线程池、连接池等共享资源。
  • ID 生成器。
  • Spring 容器中的默认单例 Bean。

优点

  • 节省资源,避免重复创建。
  • 提供统一访问入口。
  • 可以控制实例生命周期。

缺点

  • 容易变成全局变量,增加隐藏依赖。
  • 单例内部如果有可变状态,多线程下仍然要考虑并发安全。
  • 单元测试时不如普通对象容易替换。
  • 过度使用会让代码耦合变重。

实战注意

在 Spring 项目里,大多数对象不需要自己手写单例。Spring Bean 默认就是单例作用域,通常让容器管理即可。

真正需要手写单例的场景,多出现在框架工具类、SDK、非 Spring 管理对象中。

以工程实践沉淀知识,以文档复盘成长。