线程安全的单例有多种写法,推荐两种:静态内部类和枚举。
静态内部类方式利用类加载的线程安全性:内部类只有在首次被调用 getInstance 时才会被加载并初始化,JVM 保证类初始化过程加锁,天然线程安全,且实现了懒加载。枚举方式更简洁,JVM 保证枚举实例唯一,还能防御反射攻击和反序列化破坏单例。
经典的"双重检查锁"(DCL)写法必须配合 volatile:因为 instance = new Singleton() 不是原子操作(分配内存、初始化、赋值引用三步),没有 volatile 时指令重排序可能让其他线程拿到未初始化完成的对象。两次判空则是为了在已初始化后不再进入同步块,降低锁开销。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
追问方向:为什么 DCL 需要 volatile?反序列化如何破坏单例(readResolve)?
(约 330 字)