序列化对单例的破坏
首先来写一个单例模式的类(为了比较equals的效果,我加进了一个实例字段并重写了hashCode和equals方法,这不重要):
public class Singleton implements Serializable{
private int field = 3;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + field;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Singleton other = (Singleton) obj;
if (field != other.field)
return false;
return true;
}
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
接下来测试在序列化场景下的情况:
public class SerializableDemo1 {
//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
System.out.println(newInstance.equals(Singleton.getSingleton()));
}
}
结果为 false true。这说明,通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了单例性。
原因
简单来说,
ObjectInputStream
的
readObject
方法的调用栈包含如下关键代码:
ObjectStreamClass desc = readClassDesc(false);
// ...略
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
}
上述代码通过反射调用构造器,新建了一个对象。所以单例模式被破坏了。那么如何解决呢?
防止序列化破坏单例模式
只要在Singleton类中增加
readResolve
方法即可:
private Object readResolve() {
return singleton;
}
可通过查看
ObjectInputStream
的
readObject
方法的调用栈的后续代码得知原因:
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
// ...略
if (rep != obj) {
// ...略
handles.setObject(passHandle, obj = rep);
}
}
简单来说,
hasReadResolveMethod
方法会判断实现了
serializable
或者
externalizable
接口的类
desc
中是否包含
readResolve
方法。如果是,则会通过反射调用该类的
readResolve
方法,并用返回值覆盖以前的obj?
主要参考:单例与序列化的那些事儿