說到這個話題,我先拋出單例的餓漢式寫法安全
public class HungrySingleton { private HungrySingleton() { } private static final HungrySingleton hungry = new HungrySingleton(); public static HungrySingleton getInstance() { return hungry; } }
首先需讓HungrySingleton支持序列化, 修改HungrySingleton類函數
public class HungrySingleton implements Serializable {
寫一個 測試類對該餓漢式進行序列化、反序列化測試
public class Client { public static void main(String[] args) { HungrySingleton s1 = HungrySingleton.getInstance(); HungrySingleton s2 = null; try { // 將s1序列化到磁盤 FileOutputStream fos = new FileOutputStream("a.obj"); @Cleanup ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.flush(); @Cleanup FileInputStream fis = new FileInputStream("a.obj"); ObjectInputStream ois = new ObjectInputStream(fis); // 從磁盤反序列化 s2 = (HungrySingleton) ois.readObject(); System.out.println(s1 == s2); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
運行結果
ui
從運行結果能夠看出,序列化破壞了單例,產生了多個實例。
那咱們如何解決呢?
在HungrySingleton 類中添加 readResolve()方法就能夠完美解決spa
public class HungrySingleton implements Serializable { private HungrySingleton() { } private static final HungrySingleton hungry = new HungrySingleton(); public static HungrySingleton getInstance() { return hungry; } // 咱們添加的readResolve()方法 private Object readResolve() { return hungry; } }
如上添加後,運行Client咱們對測試類,能夠看到打印出true。
你們必定會有疑問,readResolve這個方法爲啥能夠解決序列化破壞單例的問題3d
源頭就在於咱們測試類中的code
ObjectInputStream ois = new ObjectInputStream(fis); singleton1 = (HungrySingleton) ois.readObject();// 這句代碼是咱們的入手點
進到ObjectInputStream#readObject()看源碼,try第一句代碼就是對象
//返回的obj對象,就是ObjectInputStream的readObject0返回的對象。 Object obj = readObject0(false);
進入ObjectInputStream#readObject0(),switch語句對枚舉或者Object類都有對應的序列化機制
重點代碼blog
case TC_ENUM: // 這句代碼是針對枚舉,單例中爲啥枚舉式最安全,就是看這行代碼,後續,小夥伴能夠研讀研讀 return checkResolve(readEnum(unshared)); case TC_OBJECT: //咱們的Object 類 return checkResolve(readOrdinaryObject(unshared));
checkResolve:檢查對象,並替換
readOrdinaryObject:讀取二進制對象
咱們先進入readOrdinaryObject()方法圖片
try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); }
能夠看到,readOrdinaryObject()方法是經過desc.isInstantiable() 來判斷是否須要new一個對象,若是返回true,方法經過反射的方式調用無參構造方法新建一個對象,不然,返回空。
那咱們進入isInstantiable()方法,
boolean isInstantiable() { requireInitialized(); //cons是構造函數 return (cons != null); }
cons != null是判斷類的構造方法是否爲空,咱們你們應該知道,Class類的構造方法確定不爲空,顯然isInstantiable()返回true,也就是說,必定會new 一個對象,且被obj接收。
咱們回到readOrdinaryObject()方法,查看初始化完成後的操做。
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { // !敲重點,這句代碼塊是爲何會執行咱們在單例中定義的readResolve()方法的核心。 Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } }
咱們要敲黑板了,這裏就是單例類中定義readResolve就能夠解決問題的關鍵所在!
若"obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()"這條語句返回true
就會調用Object rep = desc.invokeReadResolve(obj) 這條語句。
咱們進入hasReadResolveMethod()方法
boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
"readResolveMethod != null "的判斷,咱們深究進去,readResolveMethod是從哪裏讀取進來的
/** class-defined readResolve method, or null if none */ // 定義readResolveMethod 的方法 private Method readResolveMethod; // 對readResolveMethod賦值, 經過反射得到類中名爲readResolve的方法 readResolveMethod = getInheritableMethod( cl, "readResolve", null, Object.class);
也就是說!
若目標類有readResolve方法,那就經過反射的方式調用要被反序列化的類中的readResolve方法,返回一個對象,而後把這個新的對象複製給以前建立的obj(即最終返回的對象)。那被反序列化的類中的readResolve 方法裏是什麼?就是直接返回咱們的單例對象。
再次貼上來,咱們的單例類。
public class HungrySingleton implements Serializable { private HungrySingleton() { } private static final HungrySingleton hungry = new HungrySingleton(); public static HungrySingleton getInstance() { return hungry; } private Object readResolve() { return hungry; } }
嘻嘻,若有不對之處還但願各位留言指正,以避免誤導他人。