序列化對單例的破壞
首先來寫一個單例模式的類(為了比較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?
主要參考:單例與序列化的那些事兒