從原始碼分析如何避免發射和序列化破壞單利模式
序列化對單例的破壞
首先來寫一個單例的類:
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 2
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,說明:
通過對Singleton的序列化與反序列化得到的物件是一個新的物件,這就破壞了Singleton的單例性。
這裡,在介紹如何解決這個問題之前,我們先來深入分析一下,為什麼會這樣?在反序列化的過程中到底發生了什麼。
ObjectInputStream
物件的序列化過程通過ObjectOutputStream和ObjectInputputStream來實現的,那麼帶著剛剛的問題,分析一下ObjectInputputStream 的readObject
方法執行情況到底是怎樣的。
為了節省篇幅,這裡給出ObjectInputStream的readObject
這裡看一下重點程式碼,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);
}
這裡建立的這個obj物件,就是本方法要返回的物件,也可以暫時理解為是ObjectInputStream的readObject
返回的物件。
isInstantiable
:如果一個serializable/externalizable的類可以在執行時被例項化,那麼該方法就返回true。針對serializable和externalizable我會在其他文章中介紹。
desc.newInstance
:該方法通過反射的方式呼叫無參構造方法新建一個物件。
所以。到目前為止,也就可以解釋,為什麼序列化可以破壞單例了?
答:序列化會通過反射呼叫無引數的構造方法建立一個新的物件。
那麼,接下來我們再看剛開始留下的問題,如何防止序列化/反序列化破壞單例模式。
防止序列化破壞單例模式
先給出解決方案,然後再具體分析原理:
只要在Singleton類中定義readResolve
就可以解決該問題:
private Object readResolve() { return singleton; }
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方法,並在該方法中指定要返回的物件的生成策略,就可以防止單例被破壞。