本文將通過實例+閱讀Java源碼的方式介紹序列化是如何破壞單例模式的,以及如何避免序列化對單例的破壞。
單例模式,是設(shè)計模式中最簡單的一種。通過單例模式可以保證系統(tǒng)中一個類只有一個實例而且該實例易于外界訪問,從而方便對實例個數(shù)的控制并節(jié)約系統(tǒng)資源。如果希望在系統(tǒng)中某個類的對象只能存在一個,單例模式是最好的解決方案。關(guān)于單例模式的使用方式,可以閱讀單例模式的七種寫法
但是,單例模式真的能夠?qū)崿F(xiàn)實例的唯一性嗎?
答案是否定的,很多人都知道使用反射可以破壞單例模式,除了反射以外,使用序列化與反序列化也同樣會破壞單例。
首先來寫一個單例的類:
code 1
package com.hollis; import java.io.Serializable; /** * Created by hollis on 16/2/5. * 使用雙重校驗鎖方式實現(xiàn)單例 */ public class Singleton implements Serializable{ 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; } }
接下來是一個測試類:
code 2
package com.hollis; import java.io.*; /** * Created by hollis on 16/2/5. */ public class SerializableDemo1 { //為了便于理解,忽略關(guān)閉流操作及刪除文件操作。真正編碼時千萬不要忘記 //Exception直接拋出 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()); } } //false
輸出結(jié)構(gòu)為false,說明:
通過對Singleton的序列化與反序列化得到的對象是一個新的對象,這就破壞了Singleton的單例性。
這里,在介紹如何解決這個問題之前,我們先來深入分析一下,為什么會這樣?在反序列化的過程中到底發(fā)生了什么。
對象的序列化過程通過ObjectOutputStream和ObjectInputputStream來實現(xiàn)的,那么帶著剛剛的問題,分析一下ObjectInputputStream 的readObject
方法執(zhí)行情況到底是怎樣的。
為了節(jié)省篇幅,這里給出ObjectInputStream的readObject
的調(diào)用棧:
這里看一下重點代碼,readOrdinaryObject
方法的代碼片段: code 3
private Object readOrdinaryObject(boolean unshared) throws IOException { //此處省略部分代碼 Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } //此處省略部分代碼 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { handles.setObject(passHandle, obj = rep); } } return obj; }
code 3 中主要貼出兩部分代碼。先分析第一部分:
code 3.1
Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex); }
這里創(chuàng)建的這個obj對象,就是本方法要返回的對象,也可以暫時理解為是ObjectInputStream的readObject
返回的對象。
isInstantiable
:如果一個serializable/externalizable的類可以在運行時被實例化,那么該方法就返回true。針對serializable和externalizable我會在其他文章中介紹。
desc.newInstance
:該方法通過反射的方式調(diào)用無參構(gòu)造方法新建一個對象。
所以。到目前為止,也就可以解釋,為什么序列化可以破壞單例了?
答:序列化會通過反射調(diào)用無參數(shù)的構(gòu)造方法創(chuàng)建一個新的對象。
那么,接下來我們再看剛開始留下的問題,如何防止序列化/反序列化破壞單例模式。
先給出解決方案,然后再具體分析原理:
只要在Singleton類中定義readResolve
就可以解決該問題:
code 4
package com.hollis; import java.io.Serializable; /** * Created by hollis on 16/2/5. * 使用雙重校驗鎖方式實現(xiàn)單例 */ public class Singleton implements Serializable{ 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; } private Object readResolve() { return singleton; } }
還是運行以下測試類:
package com.hollis; import java.io.*; /** * Created by hollis on 16/2/5. */ public class SerializableDemo1 { //為了便于理解,忽略關(guān)閉流操作及刪除文件操作。真正編碼時千萬不要忘記 //Exception直接拋出 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()); } } //true
本次輸出結(jié)果為true。具體原理,我們回過頭繼續(xù)分析code 3中的第二段代碼:
code 3.2
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { handles.setObject(passHandle, obj = rep); } }
hasReadResolveMethod
:如果實現(xiàn)了serializable 或者 externalizable接口的類中包含readResolve
則返回true
invokeReadResolve
:通過反射的方式調(diào)用要被反序列化的類的readResolve方法。
所以,原理也就清楚了,主要在Singleton中定義readResolve方法,并在該方法中指定要返回的對象的生成策略,就可以防止單例被破壞。
在涉及到序列化的場景時,要格外注意他對單例的破壞。