单例模式真的能够实现实例的唯一性吗?
答案是否定的,很多人都知道使用反射可以破坏单例模式,除了反射以外,使用序列化与反序列化也同样会破坏单例。
直接上例子
这里通过双重校验锁的方式编写一个单例模式
public class Singleton implements Serializable { private static volatile Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance=new Singleton(); } } } return instance; } }
测试类:
public static void main(String[] args) { try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(Singleton.getInstance()); File file = new File("tempFile"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Singleton newInstance = (Singleton) ois.readObject(); System.out.println(newInstance == Singleton.getInstance()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
输出的结果是flase 很显然是两个对象 并不符合单例模式的定义
ObjectInputStream
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,分析一下ObjectInputputStream 的readObject
方法执行情况到底是怎样的。
为了节省篇幅,这里给出ObjectInputStream的readObject
的调用栈:
这里看一下重点代码,readOrdinaryObject
方法的代码片段: code 3
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//此处省略部分代码
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);
}
//此处省略部分代码
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
code 3.1
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);
}
这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject
返回的对象。
isInstantiable
:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。
desc.newInstance
:该方法通过反射的方式调用无参构造方法新建一个对象。
所以。到目前为止,也就可以解释,为什么序列化可以破坏单例了?
答:序列化会通过反射调用无参数的构造方法创建一个新的对象。
那么,接下来我们再看刚开始留下的问题,如何防止序列化/反序列化破坏单例模式。
防止序列化破坏单例模式
先给出解决方案,然后再具体分析原理:
只要在Singleton类中定义readResolve
就可以解决该问题:
public class Singleton implements Serializable {
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance=new Singleton();
}
}
}
return instance;
}
//添加此方法阻止序列化时重新生成对象
private Object readResolve() {
return instance;
}
}
在此测试main函数中的方法
本次输出结果为true。具体原理,我们回过头继续分析code 3中的第二段代码:
code 3.2
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
hasReadResolveMethod
:如果实现了serializable 或者 externalizable接口的类中包含readResolve
则返回true
invokeReadResolve
:通过反射的方式调用要被反序列化的类的readResolve方法。
所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
总结
在涉及到序列化的场景时,要格外注意他对单例的破坏。