0
点赞
收藏
分享

微信扫一扫

深入了解设计模式之单例模式(二)

琛彤麻麻 2022-04-18 阅读 36

接上回之讲,我们通过反射来破坏单例模式,那么还有什么方法呢?

序列化破坏单例模式

一个单例对象创建好后,有时候需要将对象序列化写入磁盘,下次使用时再从磁盘读取进行反序列化,转化成内存里的对象。反序列化后的对象需要重新分配内存空间,重新创建。我们来看一段代码:

public class Solution implements Serializable {

    private static final Solution INSTANCE = new Solution();

    private Solution() {
    }

    public static Solution getInstance() {
        return INSTANCE;
    }

}

class Test {
    public static void main(String[] args) {
        Solution s1 = Solution.getInstance();
        Solution s2 = null;
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("solution.txt"));
            outputStream.writeObject(s1);
            outputStream.flush();
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("solution.txt"));
            s2 = (Solution) inputStream.readObject();
            inputStream.close();
            System.out.println(s1 == s2);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

 

从运行结果看,反序列化后的对象和手动创建的对象是不一致的,违背了单例模式的初衷,那么,我们该如何保证在序列化的情况下也能实现单例模式呢?其实很简单,只需要增加readResolve()方法即可,来看优化后的代码:

public class Solution implements Serializable {

    private static final Solution INSTANCE = new Solution();

    private Solution() {
    }

    public static Solution getInstance() {
        return INSTANCE;
    }

    public Object readResolve(){
        return INSTANCE;
    }
}

 再看运行结果,结果如下:

大家一定会想,这是什么原因呢,为什么要这么写,不如我们来剖析一下JDK的源码来看看发生了什么。我们进入ObjectInputStream类的readObject()方法,代码如下:

 private final Object readObject(Class<?> type)
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        if (! (type == Object.class || type == String.class))
            throw new AssertionError("internal error");

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(type, false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

我们发现,在readObject()方法里又调用了readObject0()方法,进入readObject0()方法,代码如下:
 

 private Object readObject0(Class<?> type, boolean unshared) throws IOException {

 ...

                case TC_OBJECT:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an object to java.lang.String");
                    }
                    return checkResolve(readOrdinaryObject(unshared));

 ...

接下来我们进入readOrdinaryObject(unshared)方法:

 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);
        }
...
        return obj;
    }

我们发现调用了isInstantiable()方法,进去查看一下:

   boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }

上述代码非常简单,就是判断构造函数是否为空,不为空就返回true,然后调用其无参构造函数实例化,但我们还没找到加上readResolve()方法就可以避免单例模式被破坏的真正原因,再继续往下看:

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) {
                // 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;
    }

判断无参构造方法存在后,又调用了hasReadResolveMethod()方法,来看代码:

   boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }

就是判断readResolveMethod是否为空,不为空就返回true,现在继续往下看,如果存在readResolve()方法就通过反射调用其方法:

 Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

通过JDK源码我们可以看出,虽然增加这个方法解决了问题,但实际上实例化了两次,只不过新创建的没被返回而已,那么如何真正意义上的解决这个问题呢?

枚举式单例模式

public enum Solution {

    INSTANCE;

    public static Solution getInstance() {
        return INSTANCE;
    }
}

我们来看运行结果:

下个文章将从源码角度揭开它神奇的面纱,太晚了,肝不动了。。。 

举报

相关推荐

0 条评论