目錄
序列化、反序列化對單例的破壞
原因分析
解決方案及解析
序列化、反序列化對單例的破壞
單例模式是工作中高頻使用的設計模式之一。單例模式可以確定記憶體中單例類隻有一個執行個體,有效的減少了記憶體的開銷,避免了類的重複建立和銷毀。
序列化意義是将實作序列化的Java對象轉換成位元組序列 ,這些位元組序列可以被儲存在磁盤上,或者通過網絡傳輸。以備以後重新恢複成原來的對象。
對于單例類使用序列化、反序列化操作時,會破壞單例(序列化前的對象和反序列化後得到的對象記憶體位址不同),示範如下:
import java.io.*;
public class LazySingleTon implements Serializable {
private LazySingleTon(){
}
public static LazySingleTon getInstance(){
return InnerClass.lazySingleTon;
}
private static class InnerClass{
private static LazySingleTon lazySingleTon = new LazySingleTon();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(LazySingleTon.getInstance());
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
LazySingleTon newInstance = (LazySingleTon) ois.readObject();
//判斷是否是同一個對象
System.out.println(newInstance);
System.out.println(LazySingleTon.getInstance());
System.out.println(newInstance == LazySingleTon.getInstance());
}
}
運作結果:
[email protected]
[email protected]
false
運作結果顯示通過序列化前後對象不同,表明單例已經被破壞了
原因分析
以不求甚解的姿勢對底層源碼進行分析一波
從ois.readObject()這個方法為入口,即ObjectInputStream類的readObject方法
找到readObject0方法中的switch片段,判斷反序列化對象類型,此時對象類型是Object
傳回值會調用readOrdinaryObject方法,readOrdinaryObject方法中的三目允許算符判斷了對象是不是可執行個體化的,如果是可執行個體化的會通過newInstance()方法反射執行個體化一個新的對象,是以序列化前的對象和反序列化後得到的對象不同!
解決方案及解析
解決方案是在單例類中加一個readResolve方法
public class LazySingleTon implements Serializable {
//其他方法,略
/**
* 解決序列化、反序列化破壞單例
* @return
*/
public Object readResolve(){
return getInstance();
}
}
再次運作main方法輸出:
[email protected]
[email protected]
true
可以看到這次序列化前後對象一緻,單例沒有被破壞
那為什麼加一個readResolve方法就能阻止單例被破壞呢?
在剛才分析的readOrdinaryObject方法有調用hasReadResolveMethod的判斷,這個方法是驗證目标類是否包含一個方法名為readResolve的方法,如果有就執行desc.invokeReadResolve,通過反射調用單例類的LazySingleTon的readResolve方法,即我們剛才加的readResolve方法,并将獲得的對象傳回,是以序列化前後對象相同!阻止了單例被破壞
文章内容參考自慕課網
設計模式學習友情連結:
單例模式的懶漢式為什麼是線程不安全的,懶漢式如何實作線程安全
設計模式【建立型模式】
這位小可愛,如果覺得文章不錯,請關注或點贊 (-__-)謝謝