加readResolve方法可修复反序列化破坏单例问题:它在反序列化末尾将新实例替换为已有静态实例,确保==为true;须实现Serializable、签名严格为private Object readResolve()、返回唯一实例,且不防反射攻击。

直接加 private Object readResolve() 方法,返回已有的静态实例,就能守住单例在反序列化场景下的唯一性。它不改序列化过程,只在反序列化“拼完对象”的最后一刻,把新对象换成老实例——所以 == 比较始终为 true。
readResolve 必须满足的硬性条件
JVM 只有在全部满足以下要求时,才会自动调用该方法:
- 类必须实现 Serializable 接口
- 方法签名严格为 private Object readResolve() throws ObjectStreamException
- 返回值必须是那个早已初始化好的唯一实例(如
return INSTANCE;或return getInstance();) - 不能是 public/protected/default —— 修饰符必须是 private
典型写法与关键细节
推荐使用静态内部类方式初始化单例,确保 INSTANCE 在调用 readResolve 前已完成加载:
public class Singleton implements Serializable {private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
if (Holder.INSTANCE != null) {
throw new RuntimeException("Singleton already initialized");
}
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
private Object readResolve() {
return getInstance();
}
}
- INSTANCE 是 static final,由 JVM 类初始化机制保证线程安全和提前就绪
- 构造器中加了非空校验,防御反射攻击(readResolve 对反射无效)
- 所有非 transient 字段应确保不会在 readResolve 执行前被误用或污染
它能防什么、不能防什么
readResolve 是专治反序列化的“最后一道闸”:
- ✅ 防止反序列化产生新实例:无论序列化多少次,反序列化结果都指向同一内存地址
- ✅ 保持集合类字段语义一致:比如单例里有个
private List<String> cache = new ArrayList<>();,只要返回的是原始实例,这个 cache 就是那个已预热、已注册、带定制行为的集合 - ❌ 不防反射攻击:通过
setAccessible(true)调用私有构造器仍可新建对象,需配合构造器内校验 - ❌ 不防多类加载器场景:不同 ClassLoader 加载的同一个类,会产生各自独立的 INSTANCE
比 readResolve 更省心的方案:直接用枚举
如果单例不需要继承其他类、也不需要实现复杂接口,最稳妥的做法是:
public enum Singleton {INSTANCE;
public void doWork() { /* ... */ }
}
- JVM 强制保障枚举反序列化时返回已有常量,不走构造器,不依赖 readResolve
- 天然免疫反射、多类加载器、序列化三重破坏路径
- 代码极简,无初始化时机争议,无字段声明烦恼

















