1. 单例模式五种实现(饿汉/懒汉/双检锁/静态内部类/枚举)的线程安全性对比?反射与反序列化如何破坏单例、如何防御?
请对比单例模式五种实现(饿汉/懒汉/双检锁/静态内部类/枚举)的线程安全性,并说明反射与反序列化如何破坏单例、如何防御?
- 五种实现的线程安全对比
- 双检锁的 volatile 与可见性
- 反射/反序列化破坏单例与防御
-
饿汉(eager):类加载时初始化实例,线程安全(JVM 保证类加载唯一),但类加载即创建,可能浪费资源。
-
懒汉(lazy):首次调用才创建,用 synchronized 方法保证线程安全,但每次调用加锁,性能差。
-
双检锁(DCL):首次检查 + 加锁 + 二次检查,实例用 volatile 修饰,防止重排序导致拿到半初始化对象;线程安全且性能好,是推荐的懒加载实现。
-
静态内部类(holder):静态内部类持有实例,只有首次访问内部类时才初始化,由 JVM 类加载保证线程安全,无锁,推荐。
-
枚举(enum):JVM 天然保证枚举单例的线程安全、反序列化安全、反射防御,是最简单且最安全的实现。
-
反射破坏:通过
Constructor.setAccessible(true)调用私有构造器可创建新实例,破坏单例。防御:构造器内检查 instance 非空则抛异常,或使用枚举(JVM 禁止反射创建枚举实例)。 -
反序列化破坏:反序列化会创建新实例(不调用构造器)。防御:实现
readResolve()返回现有 instance,或使用枚举(JVM 对枚举反序列化保证单例)。
线程安全轻松实现靠枚举/静态内部类;反射通过构造器防,反序列化通过 readResolve/枚举防。枚举是最安全实现,但无法继承。
// 双检锁
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;
}
// 防御反序列化
protected Object readResolve() { return instance; }
}
// 枚举
enum SingletonEnum { INSTANCE; }