序列化對單例模式的破壞

序列化對單例的破壞

首先來寫一個單例的類:java

code 1測試

package com.hollis; import java.io.Serializable; /** * Created by hollis on 16/2/5. * 使用雙重校驗鎖方式實現單例 */ 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 2spa

package com.hollis; import java.io.*; /** * Created by hollis on 16/2/5. */ public class SerializableDemo1 { //爲了便於理解,忽略關閉流操做及刪除文件操做。真正編碼時千萬不要忘記 //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

輸出結構爲false,說明:code

經過對Singleton的序列化與反序列化獲得的對象是一個新的對象,這就破壞了Singleton的單例性。對象

這裏,在介紹如何解決這個問題以前,咱們先來深刻分析一下,爲何會這樣?在反序列化的過程當中到底發生了什麼。接口

ObjectInputStream

對象的序列化過程經過ObjectOutputStream和ObjectInputputStream來實現的,那麼帶着剛剛的問題,分析一下ObjectInputputStream 的readObject 方法執行狀況究竟是怎樣的。get

爲了節省篇幅,這裏給出ObjectInputStream的readObject的調用棧:it

未命名文件 (1)

這裏看一下重點代碼,readOrdinaryObject方法的代碼片斷: code 3io

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); }

這裏建立的這個obj對象,就是本方法要返回的對象,也能夠暫時理解爲是ObjectInputStream的readObject返回的對象。
未命名文件 (5)

isInstantiable:若是一個serializable/externalizable的類能夠在運行時被實例化,那麼該方法就返回true。針對serializable和externalizable我會在其餘文章中介紹。

desc.newInstance:該方法經過反射的方式調用無參構造方法新建一個對象。

因此。到目前爲止,也就能夠解釋,爲何序列化能夠破壞單例了?

答:序列化會經過反射調用無參數的構造方法建立一個新的對象。

那麼,接下來咱們再看剛開始留下的問題,如何防止序列化/反序列化破壞單例模式。

防止序列化破壞單例模式

先給出解決方案,而後再具體分析原理:

只要在Singleton類中定義readResolve就能夠解決該問題:

code 4

package com.hollis; import java.io.Serializable; /** * Created by hollis on 16/2/5. * 使用雙重校驗鎖方式實現單例 */ 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 { //爲了便於理解,忽略關閉流操做及刪除文件操做。真正編碼時千萬不要忘記 //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

本次輸出結果爲true。具體原理,咱們回過頭繼續分析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:若是實現了serializable 或者 externalizable接口的類中包含readResolve則返回true

invokeReadResolve:經過反射的方式調用要被反序列化的類的readResolve方法。

因此,原理也就清楚了,主要在Singleton中定義readResolve方法,並在該方法中指定要返回的對象的生成策略,就能夠防止單例被破壞。

總結

在涉及到序列化的場景時,要格外注意他對單例的破壞。

相關文章
相關標籤/搜索