Skip to content

单例模式(Singleton Pattern)

单例模式是最常用的设计模式之一,用于确保一个类只有一个实例,并提供全局访问点。

1. 应用场景

  • 配置管理:全局配置信息只需要一个实例
  • 日志记录器:整个应用共享一个日志实例
  • 数据库连接池:管理数据库连接资源
  • 线程池:统一管理线程资源
  • 缓存:全局缓存管理

2. 实现方式

2.1 饿汉式(线程安全)

类加载时就创建实例,简单但可能浪费资源。

java
public class Singleton {
    // 类加载时就创建实例
    private static final Singleton INSTANCE = new Singleton();
    
    // 私有构造函数
    private Singleton() {}
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

优点

  • 线程安全(JVM 类加载机制保证)
  • 实现简单

缺点

  • 类加载就创建,可能造成资源浪费
  • 无法传递参数

2.2 懒汉式(非线程安全)

延迟加载,但多线程下不安全。

java
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();  // 多线程下可能创建多个实例
        }
        return instance;
    }
}

⚠️ 警告:这种写法在多线程环境下是不安全的,不推荐使用!


2.3 懒汉式 + synchronized(线程安全但低效)

java
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    // 整个方法加锁,性能差
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

缺点:每次调用都要获取锁,性能开销大。


2.4 双重检查锁(DCL,推荐)⭐

java
public class Singleton {
    // 必须使用 volatile 防止指令重排序
    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?

instance = new Singleton() 这行代码实际上分三步执行:

  1. 分配内存空间
  2. 初始化对象
  3. 将 instance 指向分配的内存

由于 JVM 指令重排序,可能变成 1 → 3 → 2,导致其他线程获取到未初始化完成的对象。

volatile 的作用

  • 禁止指令重排序
  • 保证可见性

2.5 静态内部类(推荐)⭐

java
public class Singleton {
    private Singleton() {}
    
    // 静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点

  • 延迟加载:外部类加载时,内部类不会加载
  • 线程安全:JVM 类加载机制保证
  • 无锁:性能优秀

原理

  • SingletonHolder 只有在 getInstance() 被调用时才会加载
  • 类加载过程是线程安全的(由 JVM 保证)

2.6 枚举(最佳实践)⭐⭐⭐

java
public enum Singleton {
    INSTANCE;
    
    // 可以添加方法
    public void doSomething() {
        System.out.println("执行操作");
    }
}

// 使用
Singleton.INSTANCE.doSomething();

优点

  • 绝对线程安全:JVM 保证枚举实例唯一
  • 防止反序列化重新创建对象
  • 防止反射攻击
  • 代码简洁

《Effective Java》推荐:单元素的枚举类型是实现单例的最佳方法。


3. 各实现方式对比

实现方式线程安全延迟加载防反射防反序列化推荐度
饿汉式⭐⭐
懒汉式(无锁)
懒汉式(synchronized)
双重检查锁(DCL)⭐⭐⭐
静态内部类⭐⭐⭐
枚举⭐⭐⭐⭐⭐

4. 防止反射攻击

普通单例可以被反射破坏:

java
// 反射破坏单例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance();

System.out.println(Singleton.getInstance() == instance2);  // false

防护措施

java
public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {
        // 防止反射攻击
        if (instance != null) {
            throw new RuntimeException("单例已存在,禁止反射创建!");
        }
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

5. 防止反序列化破坏

java
public class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    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;
    }
    
    // 防止反序列化创建新对象
    private Object readResolve() {
        return instance;
    }
}

6. Spring 中的单例

Spring 框架默认 Bean 是单例的(scope="singleton"):

java
@Component
public class UserService {
    // Spring 容器管理,默认单例
}

@Configuration
public class AppConfig {
    @Bean
    @Scope("singleton")  // 默认值,可省略
    public UserService userService() {
        return new UserService();
    }
}

Spring 单例 vs 设计模式单例

特性Spring 单例设计模式单例
作用范围Spring 容器内唯一JVM 内唯一
创建方式IoC 容器管理类自身控制
生命周期容器管理自行管理

7. 最佳实践

  1. 优先使用枚举:最安全、最简洁
  2. 静态内部类次之:延迟加载、无锁
  3. DCL 也可以:需确保 volatile 关键字
  4. 避免使用懒汉式无锁版本:线程不安全
  5. 在 Spring 中:直接使用容器管理的单例 Bean