Java防止单例被反序列化破坏的核心是实现private Object readResolve()方法,返回唯一实例;需实现Serializable、方法签名严格匹配、返回已初始化实例,或直接使用枚举单例。

Java 中防止单例对象在反序列化时被破坏,核心是让反序列化结果始终指向原始的唯一实例,而不是新建一个对象。关键在于利用 JVM 提供的 readResolve 机制,它能在反序列化完成的最后一步“替换”掉新创建的对象。
为什么反序列化会破坏单例
反序列化不调用构造器,而是通过反射直接分配内存并填充字段。即使你用了静态内部类、双重检查锁等线程安全写法,ObjectInputStream 仍会绕过所有初始化逻辑,生成一个全新地址的对象——导致 == 比较返回 false,单例语义失效。
加 readResolve 方法是最直接的修复方式
只要满足以下三点,JVM 就会在反序列化末尾自动调用该方法,并用其返回值覆盖刚创建的对象:
- 类必须实现
Serializable接口 - 方法签名严格为
private Object readResolve()(不能是 public/protected/default) - 方法体内返回那个早已初始化好的静态实例,例如
return getInstance();或return INSTANCE;
推荐搭配静态内部类初始化
这样能确保 INSTANCE 在 readResolve 被调用前已就绪,且线程安全无锁:
立即学习“Java免费学习笔记(深入)”;
- 用
private static class Holder { static final Singleton INSTANCE = new Singleton(); }初始化 - 构造器中加非空校验,防御反射(如
if (Holder.INSTANCE != null) throw new RuntimeException();) -
readResolve直接调用getInstance(),复用已有逻辑
注意这些常见失效场景
写了 readResolve 不代表万事大吉,以下情况会让它不生效:
- 类实现了
Externalizable接口:此时readResolve完全不触发,必须在readExternal中手动返回单例 - 用了 Kryo、Jackson 等非默认序列化库:需显式配置支持
readResolve,否则默认忽略 - Android 项目开启混淆:ProGuard 可能删掉该方法,需添加 keep 规则保留
readResolve - 字段含
transient或不可序列化类型(如ThreadLocal):这些字段反序列化后为null,readResolve不负责恢复它们
更省心的方案:直接用枚举
如果单例不需要继承、也不需要实现复杂接口,枚举是终极解法:
- JVM 强制保障枚举常量的唯一性,反序列化时直接查表复用,不走构造器也不调用
readResolve - 反射也无法创建新实例,
Constructor.newInstance()对枚举类型硬拦截并抛异常 - 无需任何额外代码,零配置即免疫两类攻击


















