天天看点

单例模式在序列化场景会怎样,如何解决原因防止序列化破坏单例模式

序列化对单例的破坏

首先来写一个单例模式的类(为了比较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?

主要参考:单例与序列化的那些事儿

继续阅读