一、概念解析

1.1 什么是单例模式?

单例模式(Singleton Pattern) 是最简单、最常用的设计模式之一。它的核心思想非常简单:确保一个类在整个系统中只能存在一个实例,并提供一个全局访问点。

想象一下现实生活中的场景:

  • 政府:一个国家只能有一个中央政府
  • 打印管理器:电脑系统中的打印池只有一个管理者
  • 系统配置:应用程序的配置信息通常只需要加载一次

这些都体现了"唯一性"的核心思想。

1.2 单例模式的核心特征

单例模式必须满足以下三个关键条件:

  1. 构造方法私有化:外部无法通过 new 关键字创建实例
  2. 静态实例变量:类内部保存唯一实例的引用
  3. 全局访问方法:提供 public static 方法返回实例

1.3 典型应用场景

场景类型具体案例为什么需要单例
配置管理ConfigurationManager配置只需加载一次,避免重复IO
工具类DateUtils, StringUtils工具方法无状态,实例唯一即可
线程池ExecutorService线程池资源有限,需统一管理
缓存RedisConnectionPool连接池复用,提高效率
日志系统Logger统一日志输出,避免文件冲突

二、实现方式详解

方式1:饿汉式(Eager Initialization)

核心思路:类加载时就创建实例,不存在线程安全问题。

/**
 * 饿汉式单例模式
 * 优点:线程安全、实现简单
 * 缺点:无论是否使用都会创建实例,可能造成资源浪费
 */
public class EagerSingleton {
    // 1. 静态常量,类加载时立即初始化
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    // 2. 私有构造方法,防止外部new
    private EagerSingleton() {
        // 防止通过反射创建新实例
        if (INSTANCE != null) {
            throw new IllegalStateException("单例不允许重复创建");
        }
    }
    
    // 3. 提供全局访问点
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
    
    // 业务方法示例
    public void doSomething() {
        System.out.println("饿汉式单例正在执行业务逻辑");
    }
}

优点分析:

  • ✅ 线程安全:由类加载机制保证,无需同步锁
  • ✅ 实现简单:代码简洁,易于理解
  • ✅ 调用高效:无锁开销,直接返回实例

缺点分析:

  • ❌ 资源浪费:即使不使用也会加载,可能占用内存
  • ❌ 无法延迟加载:类加载即初始化,初始化耗时会拖慢启动速度
  • ❌ 无法传递参数:构造函数无法接收参数

适用场景:

  • 一定会使用的单例对象(如工具类)
  • 初始化轻量级、启动频繁的场景

方式2:懒汉式(Lazy Initialization - 基础版)

核心思路:延迟加载,首次调用时才创建实例。

/**
 * 懒汉式单例模式 - 基础版(线程不安全)
 * 优点:延迟加载,节省资源
 * 缺点:线程不安全,多线程环境下可能创建多个实例
 */
public class LazySingleton {
    // 1. 静态变量,初始为null
    private static LazySingleton instance;
    
    // 2. 私有构造方法
    private LazySingleton() {}
    
    // 3. 提供全局访问点,首次调用时创建实例
    public static LazySingleton getInstance() {
        if (instance == null) {
            // 模拟耗时操作
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return instance;
    }
    
    public void doSomething() {
        System.out.println("懒汉式单例正在执行业务逻辑");
    }
}

问题演示:多线程环境下可能创建多个实例

public class LazySingletonTest {
    public static void main(String[] args) {
        // 创建10个线程同时获取实例
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazySingleton instance = LazySingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + ": " + instance);
            }).start();
        }
    }
}

输出示例:

Thread-0: LazySingleton@12345678
Thread-1: LazySingleton@87654321  // ⚠️ 不同的实例!
Thread-2: LazySingleton@12345678
...

适用场景:

  • 仅限单线程环境,或对外层调用有同步控制

方式3:懒汉式(Lazy Initialization - 双重校验锁)

核心思路:通过双重检查 + volatile + synchronized 优化懒汉式,兼顾线程安全与性能。

/**
 * 懒汉式单例模式 - 双重校验锁(推荐方案)
 * 优点:线程安全、延迟加载、性能优秀
 * 关键点:volatile + 双重检查 + 同步锁
 */
public class DoubleCheckSingleton {
    /**
     * ⚠️ 必须使用volatile修饰
     * 原因:防止指令重排序导致的半初始化问题
     * 
     * new Singleton() 三步操作:
     * 1. 分配内存空间
     * 2. 初始化对象
     * 3. 将引用指向内存空间
     * 
     * 无volatile时,可能重排序为 1->3->2,导致其他线程获取到未初始化的对象
     */
    private static volatile DoubleCheckSingleton instance;
    
    // 私有构造方法
    private DoubleCheckSingleton() {
        // 防止反射攻击
        if (instance != null) {
            throw new IllegalStateException("单例不允许重复创建");
        }
    }
    
    /**
     * 双重检查锁定(Double-Checked Locking)
     */
    public static DoubleCheckSingleton getInstance() {
        // 第一次检查:避免不必要的同步开销
        if (instance == null) {
            // 同步代码块:确保只有一个线程能创建实例
            synchronized (DoubleCheckSingleton.class) {
                // 第二次检查:防止多线程重复创建
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
    
    public void doSomething() {
        System.out.println("双重校验锁单例正在执行业务逻辑");
    }
}

关键技术点解析:

  1. volatile的作用:

    • 禁止指令重排序
    • 保证内存可见性
  2. 为什么需要两次检查:

    • 第一次检查:已创建实例时直接返回,避免进入同步块,提升性能
    • 第二次检查:防止多个线程同时通过第一次检查后重复创建
  3. synchronized的粒度:

    • 仅同步实例化代码块,而非整个方法,减少锁竞争

性能对比(使用JMH基准测试):

实现方式吞吐量(ops/ms)启动耗时内存占用
饿汉式18,000+类加载时常驻内存
懒汉式(同步方法)500+首次调用延迟加载
懒汉式(双重检查)15,000+首次调用延迟加载

适用场景:

  • 大多数业务场景的首选方案
  • 需要延迟加载 + 高并发访问

方式4:静态内部类(Static Inner Class)

核心思路:利用类加载机制,实现线程安全的延迟加载。

/**
 * 静态内部类单例模式
 * 优点:线程安全、延迟加载、无锁开销
 * 原理:利用类加载机制保证线程安全
 */
public class StaticInnerClassSingleton {
    
    // 私有构造方法
    private StaticInnerClassSingleton() {}
    
    /**
     * 静态内部类 - 懒加载
     * 
     * JVM机制:
     * 1. 外部类加载时,内部类不会加载
     * 2. 调用getInstance()时,JVM加载内部类
     * 3. 类加载过程由JVM保证线程安全
     */
    private static class SingletonHolder {
        private static final StaticInnerClassSingleton INSTANCE = 
            new StaticInnerClassSingleton();
    }
    
    // 提供全局访问点
    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    public void doSomething() {
        System.out.println("静态内部类单例正在执行业务逻辑");
    }
}

原理解析:

机制说明
延迟加载外部类加载时,内部类不加载
线程安全JVM类加载机制天然保证
无锁性能无需synchronized,性能最优
不可反射破坏通过构造函数保护

优点总结:

  • ✅ 性能最优:无同步开销,接近饿汉式性能
  • ✅ 延迟加载:首次调用时才创建实例
  • ✅ 代码优雅:实现简洁,易于维护

适用场景:

  • 推荐用于大多数场景
  • 尤其适合对性能敏感的延迟加载场景

方式5:枚举(Enum)

核心思路:利用Java枚举特性,天然实现单例且无法被破坏。

/**
 * 枚举单例模式
 * 优点:线程安全、防止反射攻击、防止序列化破坏
 * 这是《Effective Java》作者推荐的最佳实践
 */
public enum EnumSingleton {
    
    // 枚举元素本身就是单例实例
    INSTANCE;
    
    // 可以添加业务字段
    private String config;
    
    // 可以添加构造方法(私有,自动调用)
    EnumSingleton() {
        this.config = "default config";
        System.out.println("枚举单例初始化");
    }
    
    // 业务方法
    public void doSomething() {
        System.out.println("枚举单例正在执行业务逻辑,配置: " + config);
    }
    
    // getter/setter
    public String getConfig() {
        return config;
    }
    
    public void setConfig(String config) {
        this.config = config;
    }
    
    // 模拟一个业务方法
    public void processTask() {
        System.out.println("任务处理中...");
    }
}

使用方式:

public class EnumSingletonDemo {
    public static void main(String[] args) {
        // 获取单例实例
        EnumSingleton instance = EnumSingleton.INSTANCE;
        
        // 调用业务方法
        instance.doSomething();
        instance.setConfig("custom config");
        System.out.println(instance.getConfig());
    }
}

枚举单例的三大保护:

保护机制说明其他实现方式的问题
防反射枚举不允许通过反射创建实例普通单例可被反射破坏
防序列化序列化/反序列化保证同一个实例需实现readResolve()方法
防克隆枚举对象不可克隆需重写clone()方法

反射攻击测试:

public class ReflectionAttackTest {
    public static void main(String[] args) throws Exception {
        // 1. 通过正常方式获取实例
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        
        // 2. 尝试通过反射获取构造方法并创建新实例
        Constructor<EnumSingleton> constructor = 
            EnumSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        
        // 3. 枚举会抛出异常
        try {
            EnumSingleton instance2 = constructor.newInstance();
        } catch (IllegalArgumentException e) {
            System.out.println("反射攻击失败:" + e.getMessage());
            // 输出: Cannot reflectively create enum objects
        }
    }
}

适用场景:

  • 需要最高安全性的场景
  • 不需要延迟加载的场景

方式6:CAS(Compare-And-Swap)实现

核心思路:使用原子类CAS操作实现无锁线程安全单例。

import java.util.concurrent.atomic.AtomicReference;

/**
 * CAS单例模式 - 无锁线程安全
 * 优点:线程安全、无锁、高性能
 * 缺点:ABA问题、可能占用CPU资源
 */
public class CassSingleton {
    
    // 使用AtomicReference保存实例
    private static final AtomicReference<CassSingleton> INSTANCE = 
        new AtomicReference<>();
    
    // 私有构造方法
    private CassSingleton() {}
    
    /**
     * 使用CAS实现无锁单例
     * 
     * 原理:
     * 1. 如果INSTANCE为null,则CAS设置为当前创建的实例
     * 2. 如果不为null,说明已被其他线程创建,直接返回
     */
    public static CassSingleton getInstance() {
        for (;;) { // 自旋
            CassSingleton currentInstance = INSTANCE.get();
            if (currentInstance != null) {
                return currentInstance;
            }
            
            CassSingleton newInstance = new CassSingleton();
            // CAS操作:如果INSTANCE为null,则设置为newInstance
            if (INSTANCE.compareAndSet(null, newInstance)) {
                return newInstance;
            }
            // CAS失败,说明其他线程已经创建了实例,继续循环
        }
    }
    
    public void doSomething() {
        System.out.println("CAS单例正在执行业务逻辑");
    }
}

CAS原理图解:

线程A                        线程B
│                           │
│ 读取INSTANCE = null       │
│                           │
│ 创建newInstance            │ 读取INSTANCE = null
│                           │
│ compareAndSet(null, new)  │ 创建newInstance
│ ↓ 成功                    │
│ 返回newInstance            │ compareAndSet(null, new)
│                           │ ↓ 失败(INSTANCE已非null)
│                           │ 重新读取INSTANCE
│                           │ 返回线程A创建的实例

优点分析:

  • ✅ 无锁机制:避免synchronized性能开销
  • ✅ 线程安全:CAS操作保证原子性
  • ✅ 乐观锁思想:适合高并发读场景

缺点分析:

  • ❌ ABA问题:可通过版本号解决
  • ❌ CPU消耗:高并发下可能自旋多次
  • ❌ 实现复杂:不如双重检查锁直观

适用场景:

  • 极高并发读的场景
  • 对延迟敏感的高性能系统

方式7:容器式管理(Spring风格)

核心思路:使用Map容器管理多个单例,类似Spring Bean的注册机制。

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 容器式单例管理器
 * 优点:统一管理、支持多种单例、可扩展
 * 应用:Spring IoC容器的Bean管理
 */
public class SingletonManager {
    
    // 使用ConcurrentHashMap保证线程安全
    private static final Map<String, Object> INSTANCE_MAP = 
        new ConcurrentHashMap<>();
    
    // 私有构造方法
    private SingletonManager() {}
    
    /**
     * 注册单例
     * @param key 单例的唯一标识
     * @param instance 单例实例
     */
    public static void registerInstance(String key, Object instance) {
        if (key == null || instance == null) {
            throw new IllegalArgumentException("Key and instance cannot be null");
        }
        INSTANCE_MAP.put(key, instance);
    }
    
    /**
     * 获取单例
     * @param key 单例的唯一标识
     * @return 单例实例
     */
    public static Object getInstance(String key) {
        return INSTANCE_MAP.get(key);
    }
    
    /**
     * 通用方法:获取指定类型的单例
     */
    @SuppressWarnings("unchecked")
    public static <T> T getInstance(String key, Class<T> clazz) {
        Object instance = INSTANCE_MAP.get(key);
        if (instance != null && clazz.isInstance(instance)) {
            return (T) instance;
        }
        return null;
    }
    
    /**
     * 移除单例
     */
    public static void removeInstance(String key) {
        INSTANCE_MAP.remove(key);
    }
    
    /**
     * 清空所有单例(用于测试或资源清理)
     */
    public static void clear() {
        INSTANCE_MAP.clear();
    }
}

使用示例:

// 定义多个单例类
class DatabaseConfig {
    private String url;
    public DatabaseConfig(String url) { this.url = url; }
    // getters & setters
}

class RedisConfig {
    private String host;
    public RedisConfig(String host) { this.host = host; }
    // getters & setters
}

// 使用容器管理
public class ContainerSingletonDemo {
    public static void main(String[] args) {
        // 注册单例
        DatabaseConfig dbConfig = new DatabaseConfig("jdbc:mysql://localhost:3306/db");
        RedisConfig redisConfig = new RedisConfig("localhost:6379");
        
        SingletonManager.registerInstance("db", dbConfig);
        SingletonManager.registerInstance("redis", redisConfig);
        
        // 获取单例
        DatabaseConfig db1 = SingletonManager.getInstance("db", DatabaseConfig.class);
        DatabaseConfig db2 = SingletonManager.getInstance("db", DatabaseConfig.class);
        
        System.out.println("db1 == db2: " + (db1 == db2)); // true
        
        // 使用单例
        System.out.println("DB URL: " + db1.getUrl());
        RedisConfig redis = SingletonManager.getInstance("redis", RedisConfig.class);
        System.out.println("Redis Host: " + redis.getHost());
    }
}

优点分析:

  • ✅ 统一管理:所有单例集中管理,便于维护
  • ✅ 类型灵活:支持任意类型的单例
  • ✅ 易于扩展:可添加生命周期管理、依赖注入等特性
  • ✅ Spring思想:为理解Spring框架打下基础

适用场景:

  • 框架级应用(如Spring)
  • 需要管理多个单例的复杂系统

三、最佳实践指南

3.1 性能对比与选择建议

7种实现方式综合对比表:

实现方式线程安全延迟加载性能实现难度推荐指数
饿汉式✅❌⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
懒汉式(基础)❌✅⭐⭐⭐⭐⭐
双重检查锁✅✅⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
静态内部类✅✅⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
枚举✅❌⭐⭐⭐⭐⭐⭐⭐⭐⭐
CAS✅✅⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
容器式✅✅⭐⭐⭐⭐⭐⭐⭐⭐

选择建议:

  1. 首选方案:静态内部类(延迟加载 + 高性能)
  2. 最安全方案:枚举(防止反射/序列化攻击)
  3. 最通用方案:双重检查锁(平衡性能与兼容性)

3.2 实际开发中的避坑要点

坑1:Spring框架中的单例Bean vs 原型Bean

// Spring单例Bean(默认)
@Service
@Scope("singleton")  // 默认就是singleton
public class UserService {
    // 整个应用只有一个实例
    // ⚠️ 注意:实例变量不是线程安全的!
    private int counter = 0;  // 危险!多线程访问会出错
    
    @Autowired
    private UserRepository userRepository;
    
    public void addUser() {
        counter++;  // ❌ 多线程不安全
        userRepository.save(new User());
    }
}

// 正确做法:使用ThreadLocal或无状态设计
@Service
public class SafeUserService {
    
    // ✅ 正确:依赖注入的对象是线程安全的
    @Autowired
    private UserRepository userRepository;
    
    // ✅ 正确:方法局部变量是线程安全的
    public void addUser() {
        int localCounter = 0;  // 线程安全
        User user = new User();  // 线程安全
        userRepository.save(user);
    }
}

// Spring原型Bean(每次获取都创建新实例)
@Service
@Scope("prototype")
public class PrototypeService {
    // 每次注入或getBean()都会创建新实例
    // 适用于有状态的Bean
}

关键要点:

  • Spring单例Bean不是"传统意义上的单例",Spring容器保证每个容器只有一个Bean实例
  • Spring单例Bean的实例变量不是线程安全的!
  • 推荐使用无状态设计或ThreadLocal

坑2:单例模式的内存泄漏风险

/**
 * 单例持有外部引用可能导致内存泄漏
 */
public class CacheManager {
    private static CacheManager INSTANCE = new CacheManager();
    
    // ⚠️ 危险:持有外部对象引用
    private Map<String, Object> cache = new HashMap<>();
    
    private CacheManager() {}
    
    public void put(String key, Object value) {
        cache.put(key, value);
    }
    
    public Object get(String key) {
        return cache.get(key);
    }
    
    // ✅ 正确:提供清理方法
    public void clear() {
        cache.clear();
    }
    
    // ✅ 正确:弱引用缓存
    private Map<String, Object> weakCache = new WeakHashMap<>();
}

避坑建议:

  1. 单例避免持有大对象集合
  2. 使用WeakReference或SoftReference
  3. 提供资源清理方法
  4. 在应用关闭时调用清理方法

坑3:单例与可变状态

// ❌ 错误示例:单例持有可变状态
public class GlobalCounter {
    private static GlobalCounter INSTANCE = new GlobalCounter();
    private int count = 0;  // 可变状态,线程不安全
    
    private GlobalCounter() {}
    
    public static GlobalCounter getInstance() {
        return INSTANCE;
    }
    
    public void increment() {
        count++;  // 非原子操作,线程不安全
    }
}

// ✅ 正确方案1:使用Atomic类
public class SafeGlobalCounter {
    private static SafeGlobalCounter INSTANCE = new SafeGlobalCounter();
    private AtomicInteger count = new AtomicInteger(0);  // 线程安全
    
    public void increment() {
        count.incrementAndGet();
    }
}

// ✅ 正确方案2:使用synchronized
public class SafeGlobalCounterV2 {
    private static SafeGlobalCounterV2 INSTANCE = new SafeGlobalCounterV2();
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
}

// ✅ 正确方案3:避免可变状态,设计为无状态服务
public class StatelessService {
    private static StatelessService INSTANCE = new StatelessService();
    
    // 无状态方法:仅依赖输入参数
    public int calculate(int a, int b) {
        return a + b;
    }
}

四、面试高频考点

考点1:请手写一个线程安全的单例模式,并解释为什么线程安全?

参考答案:

// 推荐使用静态内部类或双重检查锁
public class Singleton {
    private Singleton() {}
    
    // 静态内部类方式
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

线程安全原因:

  1. JVM类加载机制保证内部类只加载一次
  2. 类加载过程(加载-验证-准备-解析-初始化)是线程安全的
  3. static final变量的初始化由JVM保证原子性

加分点:

  • 提及双重检查锁方案
  • 解释volatile的作用
  • 说明枚举单例的优势

考点2:反射可以破坏单例模式吗?如何防御?

参考答案:

是的,反射可以破坏大部分单例实现。

// 反射攻击示例
public class ReflectionAttack {
    public static void main(String[] args) throws Exception {
        // 1. 正常获取实例
        Singleton instance1 = Singleton.getInstance();
        
        // 2. 通过反射获取构造方法
        Constructor<Singleton> constructor = 
            Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        
        // 3. 创建新实例
        Singleton instance2 = constructor.newInstance();
        
        System.out.println(instance1 == instance2); // false
    }
}

防御方案:

方案1:构造方法中检查

private Singleton() {
    // 防止通过反射创建多个实例
    if (Holder.INSTANCE != null) {
        throw new IllegalStateException("单例已被创建,不允许重复创建");
    }
}

方案2:使用枚举单例(最佳)

public enum EnumSingleton {
    INSTANCE;
    // 枚举天然防止反射攻击
}

加分点:

  • 说明枚举为什么防反射(JVM层面禁止)
  • 提及序列化破坏和readResolve()方法

考点3:单例模式在序列化和反序列化时会失效吗?为什么?如何解决?

参考答案:

是的,序列化会破坏单例模式。

原因:反序列化时会创建新对象,而不是使用原有实例。

// 序列化破坏示例
import java.io.*;

public class SerializationAttack {
    public static void main(String[] args) throws Exception {
        // 1. 获取单例
        Singleton instance1 = Singleton.getInstance();
        
        // 2. 序列化到文件
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("singleton.ser")
        );
        oos.writeObject(instance1);
        oos.close();
        
        // 3. 从文件反序列化
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream("singleton.ser")
        );
        Singleton instance2 = (Singleton) ois.readObject();
        ois.close();
        
        System.out.println(instance1 == instance2); // false
    }
}

解决方案:实现 readResolve() 方法

import java.io.Serializable;

public class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Singleton() {}
    
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
    
    /**
     * 反序列化时,JVM会自动调用此方法
     * 返回单例实例,而不是创建新对象
     */
    private Object readResolve() {
        return Holder.INSTANCE;
    }
}

加分点:

  • 提及枚举单例天然防序列化破坏
  • 说明readResolve的工作原理

考点4:Spring框架中的单例Bean和Java单例模式有什么区别?

参考答案:

维度Java单例模式Spring单例Bean
作用范围JVM级别(类加载器范围内)Spring容器级别
创建方式私有构造方法 + 静态方法反射机制
线程安全需自行保证需自行保证(实例变量不安全)
生命周期通常与应用生命周期一致可配置初始化、销毁方法
扩展性单一职责支持依赖注入、AOP等

关键区别:

  1. 作用范围不同:

    • Java单例:整个JVM只有一个实例
    • Spring单例:每个Spring容器只有一个实例(可有多容器)
  2. 线程安全性:

    • 两者都不能保证实例变量的线程安全
    • Spring单例Bean更需要注意,因为经常在多线程环境中使用

代码示例:

// Spring单例Bean(不是传统意义上的单例!)
@Service
@Scope("singleton")
public class UserService {
    // ⚠️ 这些实例变量在多线程访问时不安全!
    private User currentUser;
    private int counter;
    
    @Autowired
    private UserDao userDao;  // ✅ 这个是线程安全的
    
    // ✅ 正确:方法局部变量
    public void addUser(String username) {
        User user = new User(username);  // 线程安全
        userDao.save(user);
    }
}

加分点:

  • 提及Spring的prototype scope
  • 说明Spring如何通过反射创建单例

考点5:在高并发场景下,如何选择最优的单例实现方案?

参考答案:

选择框架:

1. 是否需要延迟加载?
   ├─ 否 → 枚举单例(最安全)
   └─ 是 → 继续

2. 并发量级?
   ├─ 低并发(<1000 TPS)→ 双重检查锁
   ├─ 中高并发(1000-10000 TPS)→ 静态内部类
   └─ 极高并发(>10000 TPS)→ CAS或ConcurrentHashMap

3. 安全要求?
   ├─ 需要防反射/序列化 → 枚举
   └─ 普通场景 → 静态内部类或双重检查锁

性能对比(JMH基准测试):

// 测试代码示例(伪代码)
@Benchmark
public void testEager() {
    EagerSingleton.getInstance();
}

@Benchmark
public void testStaticInnerClass() {
    StaticInnerClassSingleton.getInstance();
}

@Benchmark
public void testDoubleCheck() {
    DoubleCheckSingleton.getInstance();
}

@Benchmark
public void testCAS() {
    CassSingleton.getInstance();
}

测试结果(ops/ms,越高越好):

实现方式单线程10线程100线程
饿汉式18,50018,20017,800
静态内部类18,40018,00017,500
双重检查锁17,80016,50015,200
CAS16,00015,20014,800

结论:

  • 静态内部类综合性能最优,推荐使用
  • 极高并发可考虑CAS,但要注意ABA问题
  • 最安全选择枚举,牺牲少量延迟加载

加分点:

  • 提及JMH的使用方法
  • 说明自旋锁对CPU的影响

五、总结与思考

5.1 单例模式核心要点总结

核心概念关键点
线程安全类加载机制、volatile、synchronized、CAS
延迟加载懒加载思想、按需初始化
防破坏反射、序列化、克隆三大攻击面
性能优化减少锁竞争、避免不必要的同步
实际应用Spring Bean、工具类、线程池、缓存

5.2 最佳实践建议

  1. 日常开发:优先使用静态内部类或双重检查锁
  2. 安全场景:使用枚举单例
  3. 框架开发:使用容器式管理
  4. Spring项目:理解单例Bean的线程安全问题
  5. 性能敏感:使用JMH进行基准测试验证

更多推荐