1. 程式人生 > >【單例深思】單例與序列化

【單例深思】單例與序列化

在前面的文章中提到,序列化會破壞單例模式,下面用靜態內部類的實現方式,說明序列化對單例的影響: 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[] argsthrows 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(outerHandlepassHandle);             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 unsharedthrows 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(passHandleresolveEx);         }         if (desc.isExternalizable()) {             readExternalData((Externalizable) objdesc);         } else {             readSerialData(objdesc);         }         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(passHandleobj = 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 中很常見的,通過這些分析我們明白了序列化機制對單例的影響,以及通過什麼樣的方法解決了這些影響。