序列化、反序列化对单例的破坏、原因分析、解决方案及解析
目录
序列化、反序列化对单例的破坏
单例模式是工作中高频使用的设计模式之一。单例模式可以确保内存中单例类只有一个实例,有效的减少了内存的开销,避免了类的重复创建和销毁。
序列化意义是将实现序列化的Java对象转换成字节序列 ,这些字节序列可以被保存在磁盘上,或者通过网络传输。以备以后重新恢复成原来的对象。
对于单例类使用序列化、反序列化操作时,会破坏单例(序列化前的对象和反序列化后得到的对象内存地址不同),演示如下:
import java.io.*;
public class LazySingleTon implements Serializable {
private LazySingleTon(){
}
public static LazySingleTon getInstance(){
return InnerClass.lazySingleTon;
}
private static class InnerClass{
private static LazySingleTon lazySingleTon = new LazySingleTon();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(LazySingleTon.getInstance());
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
LazySingleTon newInstance = (LazySingleTon) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance);
System.out.println(LazySingleTon.getInstance());
System.out.println(newInstance == LazySingleTon.getInstance());
}
}
运行结果:
aaa@qq.com
aaa@qq.com
false
运行结果显示通过序列化前后对象不同,表面单例已经被破坏了
原因分析
以不求甚解的姿势对底层源码进行分析一波
从ois.readObject()这个方法为入口,即ObjectInputStream类的readObject方法
找到readObject0方法中的switch片段,判断反序列化对象类型,此时对象类型是Object
返回值会调用readOrdinaryObject方法,readOrdinaryObject方法中的三目允许算符判断了对象是不是可实例化的,如果是可实例化的会通过newInstance()方法反射实例化一个新的对象,所以序列化前的对象和反序列化后得到的对象不同!
解决方案及解析
解决方案是在单例类中加一个readResolve方法
public class LazySingleTon implements Serializable {
//其他方法,略
/**
* 解决序列化、反序列化破坏单例
* @return
*/
public Object readResolve(){
return getInstance();
}
}
再次运行main方法输出:
aaa@qq.com
aaa@qq.com
true
可以看到这次序列化前后对象一致,单例没有被破坏
那为什么加一个readResolve方法就能阻止单例被破坏呢?
在刚才分析的readOrdinaryObject方法有调用hasReadResolveMethod的判断,这个方法是验证目标类是否包含一个方法名为readResolve的方法,如果有就执行desc.invokeReadResolve,通过反射调用单例类的LazySingleTon的readResolve方法,即我们刚才加的readResolve方法,并将获得的对象返回,所以序列化前后对象相同!阻止了单例被破坏
文章内容参考自慕课网
设计模式学习友情链接:
单例模式的懒汉式为什么是线程不安全的,懒汉式如何实现线程安全
这位小可爱,如果觉得文章不错,请关注或点赞 (-__-)谢谢