天天看點

單例模式在序列化場景會怎樣,如何解決原因防止序列化破壞單例模式

序列化對單例的破壞

首先來寫一個單例模式的類(為了比較equals的效果,我加進了一個執行個體字段并重寫了hashCode和equals方法,這不重要):

public class Singleton implements Serializable{
	private int field = 3;
    @Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + field;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Singleton other = (Singleton) obj;
		if (field != other.field)
			return false;
		return true;
	}
	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;
    }
}
           

接下來測試在序列化場景下的情況:

public class SerializableDemo1 {
    //為了便于了解,忽略關閉流操作及删除檔案操作。真正編碼時千萬不要忘記
    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());
        System.out.println(newInstance.equals(Singleton.getSingleton()));
    }
}
           

結果為 false true。這說明,通過對Singleton的序列化與反序列化得到的對象是一個新的對象,這就破壞了單例性。

原因

簡單來說,

ObjectInputStream

readObject

方法的調用棧包含如下關鍵代碼:

ObjectStreamClass desc = readClassDesc(false);
// ...略
Object obj;
try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
}
           

上述代碼通過反射調用構造器,建立了一個對象。是以單例模式被破壞了。那麼如何解決呢?

防止序列化破壞單例模式

隻要在Singleton類中增加

readResolve

方法即可:

private Object readResolve() {
        return singleton;
    }
           

可通過檢視

ObjectInputStream

readObject

方法的調用棧的後續代碼得知原因:

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            // ...略
            if (rep != obj) {
                // ...略
                handles.setObject(passHandle, obj = rep);
            }
        }
           

簡單來說,

hasReadResolveMethod

方法會判斷實作了

serializable

或者

externalizable

接口的類

desc

中是否包含

readResolve

方法。如果是,則會通過反射調用該類的

readResolve

方法,并用傳回值覆寫以前的obj?

主要參考:單例與序列化的那些事兒

繼續閱讀