轉自 http://www.hollischuang.com/archives/1144
本文将通過執行個體+閱讀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 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的調用棧:
readObject—>readObject0—>readOrdinaryObject—>checkResolve
這裡看一下重點代碼,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就可以解決該問題:
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;
}
}
具體原理,我們回過頭繼續分析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方法,并在該方法中指定要傳回的對象的生成政策,就可以方式單例被破壞。
總結
在涉及到序列化的場景時,要格外注意他對單例的破壞。