【單例深思】單例與序列化
阿新 • • 發佈:2019-02-09
在前面的文章中提到,序列化會破壞單例模式,下面用靜態內部類的實現方式,說明序列化對單例的影響:
publicclass Singleton implements Serializable{
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
判斷反序列化後的物件與原來的例項是否為同一個物件?
publicclass Test {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("storageFile"));
oos.writeObject(Singleton.getInstance());
// 反序列化
File file = new File("storageFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判斷是否是同一個物件
System.out.println(newInstance == Singleton.getInstance());
}
}
輸出結果為:false 說明反序列化後的例項和之前序列化的例項不是同一個物件,這就可能導致JVM中出現多個例項的情況,這樣就違背了單例模式的要求。怎麼解決這個問題呢? 之前的文章簡單提了一下,給單例實現新增readResolve() 方法即可,修改後的程式碼如下所示:
publicclass Singleton implements Serializable{
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
相比之前只有一處改動,就是新增readResolve()方法,在方法內返回了單例,重新執行一下上述的TEST類Main方法,返回結果為:true。
結果表明,反序列化後的例項和之前被序列化的例項是同一個物件,通過這個方法確實可以解決序列化對單例模式的影響。
其他的實現方式也一樣可以通過這個方法解決序列化問題。
下面我們深入瞭解一下readResolve()方法為什麼能解決這個問題?
首先,我們需要明白在沒有新增這個方法之前,為什麼返回了一個新的物件例項?
簡單地說,這個新物件是在反序列化過程中,通過反射呼叫無引數的構造方法建立的。
從原始碼角度看,使用ObjectInputStream進行反序列化時,會使用它的readObject()方法。
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false); //
obj 是要返回的物件
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
在readObject()方法內部,通過呼叫readObject0()方法返回需要被返回的物件。 private Object readObject0(boolean unshared) throws IOException { boolean oldMode = bin.getBlockDataMode(); if (oldMode) { int remain = bin.currentBlockRemaining(); if (remain > 0) { throw new OptionalDataException(remain); } else if (defaultDataEnd) { /* * Fix for 4360508: stream is currently at the end of a field * value block written via default serialization; since there * is no terminating TC_ENDBLOCKDATA tag, simulate * end-of-custom-data behavior explicitly. */ throw new OptionalDataException(true); } bin.setBlockDataMode(false); } byte tc; while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); } depth++; totalObjectRefs++; try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); // 處理物件序列化 case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException("writing aborted", ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true); bin.peek(); // force header read throw new OptionalDataException( bin.currentBlockRemaining()); } else { throw new StreamCorruptedException( "unexpected block data"); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException(true); } else { throw new StreamCorruptedException( "unexpected end of block data"); } default: throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } } finally { depth--; bin.setBlockDataMode(oldMode); } } 物件的序列化處理在 checkResolve(readOrdinaryObject(unshared)); 方法中進行。 private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } 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); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); // 添加了readResolve()方法後,就執行判斷內的程式碼 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); // 呼叫readResolve方法 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); } } return obj; } 在obj = desc.isInstantiable() ? desc.newInstance() : null; 這行程式碼中,會先判斷是否實現了serializable/externalizable,如果實現了,就採用反射的方法例項化一個物件。 在這個方法內還有一個 if 判斷,if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()), 其中 desc.hasReadResolveMethod() 用於判斷被序列化的類中是否包含 readResolve() 方法,如果包含就返回true,在這個判斷邏輯內,會執行desc.invokeReadResolve(obj);方法,該方法會反射呼叫我們宣告的 readResolve() 方法,我們在該方法內返回了序列化的物件,因此我們也就明白了readResolve() 方法的工作原理了。 這種約定式的使用方式在 Java 中很常見的,通過這些分析我們明白了序列化機制對單例的影響,以及通過什麼樣的方法解決了這些影響。
輸出結果為:false 說明反序列化後的例項和之前序列化的例項不是同一個物件,這就可能導致JVM中出現多個例項的情況,這樣就違背了單例模式的要求。怎麼解決這個問題呢? 之前的文章簡單提了一下,給單例實現新增readResolve()
在readObject()方法內部,通過呼叫readObject0()方法返回需要被返回的物件。 private Object readObject0(boolean unshared) throws IOException { boolean oldMode = bin.getBlockDataMode(); if (oldMode) { int remain = bin.currentBlockRemaining(); if (remain > 0) { throw new OptionalDataException(remain); } else if (defaultDataEnd) { /* * Fix for 4360508: stream is currently at the end of a field * value block written via default serialization; since there * is no terminating TC_ENDBLOCKDATA tag, simulate * end-of-custom-data behavior explicitly. */ throw new OptionalDataException(true); } bin.setBlockDataMode(false); } byte tc; while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); } depth++; totalObjectRefs++; try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); // 處理物件序列化 case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException("writing aborted", ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true); bin.peek(); // force header read throw new OptionalDataException( bin.currentBlockRemaining()); } else { throw new StreamCorruptedException( "unexpected block data"); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException(true); } else { throw new StreamCorruptedException( "unexpected end of block data"); } default: throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } } finally { depth--; bin.setBlockDataMode(oldMode); } } 物件的序列化處理在 checkResolve(readOrdinaryObject(unshared)); 方法中進行。 private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } 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); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); // 添加了readResolve()方法後,就執行判斷內的程式碼 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); // 呼叫readResolve方法 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); } } return obj; } 在obj = desc.isInstantiable() ? desc.newInstance() : null; 這行程式碼中,會先判斷是否實現了serializable/externalizable,如果實現了,就採用反射的方法例項化一個物件。 在這個方法內還有一個 if 判斷,if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()), 其中 desc.hasReadResolveMethod() 用於判斷被序列化的類中是否包含 readResolve() 方法,如果包含就返回true,在這個判斷邏輯內,會執行desc.invokeReadResolve(obj);方法,該方法會反射呼叫我們宣告的 readResolve() 方法,我們在該方法內返回了序列化的物件,因此我們也就明白了readResolve() 方法的工作原理了。 這種約定式的使用方式在 Java 中很常見的,通過這些分析我們明白了序列化機制對單例的影響,以及通過什麼樣的方法解決了這些影響。