单例模式
medium设计模式创建型单例模式双重检查锁
概述
单例模式确保一个类只有一个实例,并提供一个全局访问点。
适用场景:线程池、数据库连接池、配置管理、日志对象——这些资源全局只需要一个实例。
几种实现方式
1. 饿汉式
类加载时就创建实例,线程安全但不支持延迟加载:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {} // 私有构造函数
public static Singleton getInstance() {
return INSTANCE;
}
}
2. 懒汉式(线程不安全)
第一次使用时才创建,但多线程下可能创建多个实例:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 线程 A 和 B 可能同时通过判断
instance = new Singleton(); // 创建了两个实例 ❌
}
return instance;
}
}
3. 双重检查锁(DCL)
兼顾延迟加载和线程安全,工业级生产最标准的写法:
public class Singleton {
private static volatile Singleton instance; // volatile 防止指令重排
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // ① 第一次检查(避免不必要的加锁)
synchronized (Singleton.class) {
if (instance == null) { // ② 第二次检查(防止重复创建)
instance = new Singleton();
}
}
}
return instance;
}
}
为什么需要 volatile? new Singleton() 在 JVM 中分三步:(1) 分配内存 (2) 初始化对象 (3) 将引用指向内存。指令重排可能导致 (1)(3)(2) 的顺序,另一个线程在①处看到非 null 但未初始化的对象。volatile 禁止重排序。
4. 静态内部类
利用类加载机制保证线程安全和延迟加载(推荐写法):
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // 首次调用时才加载 Holder 类
}
}
5. 枚举
最简洁、最安全的方式(防反射、防序列化):
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
各实现对比
| 实现 | 延迟加载 | 线程安全 | 防反射 | 防序列化 | 推荐 |
|---|---|---|---|---|---|
| 饿汉式 | ❌ | ✅ | ❌ | ❌ | ⭐⭐ |
| DCL | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐⭐ |
| 枚举 | ❌ | ✅ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
生产高频题
双重检查锁为什么要用 volatile?
防止指令重排序。new 操作分三步(分配内存→初始化→赋值引用),重排可能导致其他线程拿到未初始化的对象。volatile 的内存屏障禁止了这种重排。