首页/开发/代码质量与评审速查/单例模式 Singleton(创建型)
设计原则与模式62 条

单例模式 Singleton(创建型)

保证一个类仅有一个实例并提供全局访问点:适合进程内共享的配置、连接池等;但全局状态会带来隐藏依赖与测试困难,多数场景用依赖注入持有单实例更可控。

单例模式是什么

单例保证一个类只有一个实例,并提供全局访问点:进程内共享的配置、连接池、注册表是常见场景。它解决访问点与生命周期的唯一性问题,代价是引入全局状态——依赖变得隐蔽,测试之间容易互相污染。多数情况下,用依赖注入容器持有一个实例更可控。

基本信息

意图唯一实例加全局访问点
实现要点私有构造、惰性初始化、线程安全
语言差异Go 常用一次性初始化原语,Java 常用静态内部类或枚举
替代方案依赖注入容器注册单实例
作用域每进程唯一,分布式部署下天然不唯一

典型使用场景

  • 进程内共享的配置读取器
  • 无状态工具类与度量注册表
  • 连接池或线程池的持有者
  • 日志与指标上报入口
  • 需要严格控制并发初始化的稀缺资源

静态内部类实现

静态内部类实现
// 类加载时惰性初始化,由虚拟机保证线程安全
class Config {
    private Config() {}                     // 私有构造,禁止外部创建

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

    static Config instance() { return Holder.INSTANCE; }
}

几种实现的差异

实现方式线程安全说明
静态内部类由类加载机制保证惰性且无需显式加锁
枚举语言层面保证序列化与反射场景也安全
双重检查锁需正确使用 volatile漏写修饰符易出可见性问题
容器单例由容器管理依赖可注入、可替换
单例的唯一作用域要写清:是每进程唯一还是每容器唯一。集群部署时它是多个实例,涉及共享状态就得另找方案。
把全局可变状态塞进单例到处读写,依赖就从签名里消失了:测试互相污染、并发问题难以复现,想替换实现时也无处下手。
测试需要隔离单例状态时,可以在启动阶段注入替身或提供重置入口;如果做不到,说明访问方式绑得太紧,先把依赖改成注入会更容易测试。

易错点

  • 全局可变状态借单例到处读写:依赖隐藏、测试互相污染
  • 双重检查锁实现遗漏关键修饰符:并发下出现可见性与重排序问题
  • 用单例绕过依赖注入:核心逻辑在测试里无法替换实现
  • 把有状态对象做成单例:请求之间数据串味,问题只在并发下复现

数据来源与参考

  • 《设计模式:可复用面向对象软件的基础》(GoF)· Singleton
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌