单例模式总结
单例模式本身是一种设计模式,是否线程安全取决于具体实现方式。以下是不同单例实现方式的线程安全性分析及优化方案:
一、线程不安全的单例实现
1. 懒汉式(未同步)
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) { // 线程A和B可能同时进入此判断instance = new Singleton(); // 导致创建多个实例}return instance;}
}
问题:多线程同时调用 getInstance()
会破坏单例性。
二、线程安全的单例实现
1. 饿汉式(线程安全)
public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance; // 类加载时已初始化,JVM保证线程安全}
}
特点:
- 优点:实现简单,无锁性能高
- 缺点:无法延迟加载(即使未使用也会初始化)
2. 懒汉式(同步方法)
public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() { // 方法级同步if (instance == null) {instance = new Singleton();}return instance;}
}
特点:
- 优点:实现简单,保证线程安全
- 缺点:每次调用都加锁,性能差(锁粒度太大)
3. 双重检查锁定(DCL,线程安全)
public class Singleton {private static volatile Singleton instance; // 必须用volatileprivate Singleton() {}public static Singleton getInstance() {if (instance == null) { // 第一次检查(无锁)synchronized (Singleton.class) { // 加锁if (instance == null) { // 第二次检查(有锁)instance = new Singleton(); // volatile禁止指令重排序}}}return instance;}
}
关键点:
volatile
防止指令重排序(避免返回未初始化的对象)- 减少锁竞争(仅首次创建时同步)
4. 静态内部类(线程安全)
public class Singleton {private Singleton() {}private static class Holder {static final Singleton INSTANCE = new Singleton(); // 类加载时初始化}public static Singleton getInstance() {return Holder.INSTANCE; // 首次调用时加载Holder类}
}
特点:
- 结合懒加载与无锁线程安全
- JVM保证类加载过程的线程安全性
5. 枚举实现(最佳实践)
public enum Singleton {INSTANCE; // 枚举实例天然单例且线程安全public void doSomething() {// 业务方法}
}
优势:
- 绝对防止反射攻击(枚举类无法通过反射创建实例)
- 自动处理序列化与反序列化
- 代码最简洁
三、线程安全单例的核心要点
实现方式 | 线程安全 | 延迟加载 | 性能 | 防反射 | 防序列化 |
---|---|---|---|---|---|
饿汉式 | ✅ | ❌ | ⭐⭐⭐⭐ | ❌ | ❌ |
同步懒汉式 | ✅ | ✅ | ⭐ | ❌ | ❌ |
双重检查锁定 | ✅ | ✅ | ⭐⭐⭐ | ❌ | ❌ |
静态内部类 | ✅ | ✅ | ⭐⭐⭐⭐ | ❌ | ❌ |
枚举 | ✅ | ❌ | ⭐⭐⭐⭐ | ✅ | ✅ |
四、常见面试陷阱
-
为什么DCL需要volatile?
- 防止指令重排序:
new Singleton()
的字节码分为三步(分配内存→初始化→引用赋值),不加volatile
可能导致其他线程获取到未初始化的对象。
- 防止指令重排序:
-
静态内部类如何保证线程安全?
- JVM在类加载时(首次访问
Holder.INSTANCE
)会加锁(ClassLoader机制),保证初始化过程线程安全。
- JVM在类加载时(首次访问
-
枚举单例为何能防反射?
- 枚举类的构造方法在反射调用时会抛出
IllegalArgumentException
,源码中明确禁止反射创建枚举实例。
- 枚举类的构造方法在反射调用时会抛出
五、总结回答
“单例模式能否保证线程安全取决于具体实现方式:
- 线程不安全实现:如无同步的懒汉式
- 线程安全实现:
- 饿汉式(简单但无法延迟加载)
- 双重检查锁定(需加
volatile
) - 静态内部类(兼顾性能与懒加载)
- 枚举(最佳实践,防反射/序列化攻击)
实际开发中推荐使用枚举或静态内部类实现单例模式。”