一.序列化会破坏单例模式
当使用双重校验锁并且使用了volatile的时候,这种看上去非常perfect的方式也可能存在单例模式被破坏的问题,那就是遇到序列化的时候。
Q:序列化之后的对象还是同一个吗
答案是 不是
public class Singleton implements Serializable{
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 Test{
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
System.out.println(newInstance == Singleton.getSingleton());
}
}
得到的结果是false
这是因为对象在进行序列化的时候,ObjectInputStream 会调用readOrdinaryObject这个方法
readOrdinaryObject方法源码在最后
Reads and returns "ordinary" (i.e., not a String, Class, ObjectStreamClass, array, or enum constant) object, or null if object's class is unresolvable (in which case a ClassNotFoundException will be associated with object's handle). Sets passHandle to object's assigned handle. 读取并返回“普通”(即不是字符串、类、ObjectStreamClass、数组或枚举常量)对象,如果对象的类不可解析,则返回null(在这种情况下,ClassNotFoundException将与对象的句柄相关联)。将passHandle设置为对象的指定句柄。
readOrdinaryObject核心代码(一)
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);
}
isInstantiable,源码的解释是
Returns true if represented class is serializable/externalizable and can be instantiated by the serialization runtime--i.e., if it is externalizable and defines a public no-arg constructor, or if it is non-externalizable and its first non-serializable superclass defines an accessible no-arg constructor. Otherwise, returns false. 如果表示的类是可序列化/可外部化的,并且可以由序列化运行时实例化,即,如果表示的类是可外部化的,并且定义了公共的无参数构造函数,或者表示的类是不可外部化的,并且其第一个不可序列化的超类定义了可访问的无参数构造函数,则返回true。否则,返回false。
简单来说 就是isInstantiable 会判断我们要序列化的这个类可不可以进行序列化,可以就返回true。为true的话通过反射调用newInstance创建一个新的对象,不可以就返回一个null,所以,序列化会破坏单例模式!
二.如何防止单例模式被破坏
一个东西只要能看到源码,那我们就肯定能根据源码去挖出想要的信息来。
readOrdinaryObject核心代码(二)
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
Returns true if represented class is serializable or externalizable and defines a conformant readResolve method. Otherwise, returns false. 如果表示的类可序列化或可外部化,并定义一致的readResolve方法,则返回true。否则,返回false。
hasReadResolveMethod:如果被序列化的类中定义了readResolve方法,就返回true
invokeReadResolve
Invokes the readResolve method of the represented serializable class and returns the result. Throws UnsupportedOperationException if this class descriptor is not associated with a class, or if the class is non-serializable or does not define readResolve. 调用表示的可序列化类的readResolve方法并返回结果。如果此类描述符未与类关联,或者该类不可序列化或未定义readResolve,则引发UnsupportdOperationException。
invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法,如果没有readResolve方法,会抛出UnsupportedOperationException。
所以,在单例类中定义readResolve方法,在readResolve方法中返回单例对象,就能防止单例模式被破坏
public class Singleton implements Serializable{
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;
}
private Object readResolve() {
return singleton;
}
}
都说反射会破坏单例模式,没想到序列化也会破坏,但是看到这里,你也可能想到了,其实序列化破坏单例模式,最根本的原因也就是因为反射创建对象破坏的!
readOrdinaryObject方法源码
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(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
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) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}