Skip to content

怎么实现线程安全的单例模式?

线程安全的单例模式确保在多线程环境下,只有一个实例被创建。常见的实现方式包括 双重检查锁(DCL)静态内部类枚举volatile + synchronized,每种方式通过同步机制或 JVM 特性保证线程安全。

关键事实

  1. 单例模式目标
    • 全局唯一实例,控制资源访问。
  2. 线程安全问题
    • 多线程并发创建实例,可能导致多个实例。
  3. 实现原则
    • 延迟加载(懒加载)或饿汉式。
    • 同步控制或利用 JVM 类加载机制。

具体实现方式

1. 双重检查锁(Double-Checked Locking, DCL)

  • 原理:结合 volatile 和 synchronized,延迟加载并确保线程安全。
  • 特点
    • volatile 防止指令重排序,确保实例初始化完成。
    • 双重检查减少锁开销。

2. 静态内部类

  • 原理:利用 JVM 类加载机制,延迟加载,线程安全由类加载器保证。
  • 特点
    • 类加载时单线程执行,天然线程安全。
    • 懒加载,调用 getInstance 时才加载。

3. 枚举

  • 原理:JVM 保证枚举实例唯一,防反射和序列化破坏。
  • 特点
    • 最简洁,线程安全无额外开销。
    • 不可继承,适合简单单例。

4. 饿汉式(静态初始化)

  • 原理:类加载时创建实例,JVM 保证线程安全。
  • 特点
    • 非懒加载,启动即创建。
    • 简单但可能浪费资源。

5. synchronized 方法

  • 原理:方法加锁,确保每次只有一个线程创建实例。
  • 特点
    • 线程安全,但每次调用锁开销大。

延伸与面试角度

  • DCL 为什么用 volatile?
    • 防止 instance = new Singleton() 的指令重排序(分配内存 → 初始化 → 赋值)。
    • 无 volatile,线程可能拿到未初始化对象。
  • 选择依据
    • DCL:懒加载 + 高性能,常用。
    • 静态内部类:优雅,推荐。
    • 枚举:防反射,简单场景。
    • 饿汉式:启动快,无并发需求。
  • 性能对比
    • DCL 和静态内部类优于同步方法(锁范围小)。
    • 枚举和饿汉式无运行时开销。
  • 反射破坏
    • 反射可通过构造器创建实例,枚举天然免疫。
    • 其他方式需加防护:
  • 实际应用
    • Spring Bean 默认单例,用 DCL 或类加载实现。
  • 面试点
    • 问“线程安全原理”时,提同步或 JVM 类加载。
    • 问“优缺点”时,比较懒加载和性能。

总结

线程安全的单例模式可用 DCL(高效懒加载)、静态内部类(优雅)、枚举(最安全)、饿汉式(简单)或同步方法实现。DCL 和静态内部类最常用,枚举防反射最佳。面试时,可写 DCL 代码并解释 volatile,展示深入理解。