【JAVA设计模式】单例模式注意事项

186 阅读4分钟

一.序列化会破坏单例模式

当使用双重校验锁并且使用了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;
    }
​