单例模式(Singleton Pattern)(二)

简介: 上文我们了解常见的单例模式的创建方式,但还有没有其他的方式呢?在日常开发中推荐使用哪种呢?本文将带你深入了解其他的单例创建方式,以及单例模式的破坏。

前言

iShot2022-12-05 00.30.04.png
上篇文章我们讲述了,单例的推荐使用方式,以及反射对单例的破坏,但文末留了一个疑问,就是序列化如何破坏一个单例,那么本篇文章就来分析一下。

序列化对于单例的破坏

Singleton.class
public class Singleton implements Serializable {

    private volatile static Singleton singleton;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}
public class SerializableTest {

    public static void main(String[] args) throws Exception{
        // 序列化对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("file.temp")
        );
        oos.writeObject(Singleton.getInstance());

        // 序列化对象输入流
        File file = new File("file.temp");
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream(file)
        );
        Singleton singleton = (Singleton) ois.readObject();

        // false
        // 通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。
        System.out.println(Singleton.getInstance() == singleton);
    }
}

为什么对象不一样?

其实问题是出在这个ois.readObject();这个读取逻辑中, 它的源码如下:

public final Object readObject() throws IOException, ClassNotFoundException {
    return readObject(Object.class);
}

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对象,这一步要做的就是反序列化对象
            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();
            }
        }
}



/**
 * Underlying readObject implementation.
 * @param type a type expected to be deserialized; non-null
 * @param unshared true if the object can not be a reference to a shared object, otherwise false
 */
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }

        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }

        depth++;
        totalObjectRefs++;
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    // check the type of the existing object
                    return type.cast(readHandle(unshared));

                case TC_CLASS:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast a class to java.lang.String");
                    }
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast a class to java.lang.String");
                    }
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

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

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

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

                case TC_EXCEPTION:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an exception to java.lang.String");
                    }
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }

                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

上面的代码可能看起来比较晦涩,因为这其中还涉及了序列化的一些机制,这个我们有机会可以拿几篇文章来分析一下这段源码(但是如果你感兴趣,欢迎联系我,探讨一下),能明确说明的是,最终它会执行如下方法:

// 匹配的是对象
case TC_OBJECT: 
        return checkResolve(readOrdinaryObject(unshared));
readOrdinaryObject
private Object readOrdinaryObject(boolean unshared) throws IOException {
        // .... 
        Object obj;
        try {
            // 通过反射创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject返回的对象。
            // isInstantiable:如果一个serializable的类可以在运行时被实例化,那么该方法就返回true
            // desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        return obj;
    }

到这个方法里面,你应该就不陌生了,所以结论就是它会通过反射调用无参数的构造方法创建一个新的对象。

如何解决?

解决这个问题其实很简单,在Java序列化机制中,提供了readResolve方法,我们在深拷贝的时候也会经常遇到,我们只需要利用此方法,就能解决此类问题,如下:

public class Singleton implements Serializable {

    private volatile static Singleton singleton;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    /**
     * 解决方案:只要在Singleton类中定义readResolve就可以解决该问题
     * 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在--就创建一个对象
     */
    private Object readResolve() {
        return singleton;
    }

}

ReadResolve实现原理

在上文所述的readOrdinaryObject方法中,会有如下逻辑:

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            // 如果实现了serializable接口的类中,并且包含readResolve方法则返回true
            desc.hasReadResolveMethod()) {
    
            // 通过反射的方式调用要被反序列化的类的readResolve方法。
            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);
                    }
                }
                // 替换obj
                handles.setObject(passHandle, obj = rep);
            }
}
readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);
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();
        }
    }

目录
相关文章
|
6月前
|
设计模式 存储 Java
Java设计模式:解释一下单例模式(Singleton Pattern)。
`Singleton Pattern`是Java中的创建型设计模式,确保类只有一个实例并提供全局访问点。它通过私有化构造函数,用静态方法返回唯一的实例。类内静态变量存储此实例,对外仅通过静态方法访问。
50 1
|
6月前
|
设计模式 Java
单例模式(Singleton Pattern)
单例模式(Singleton Pattern)
43 0
|
设计模式
设计模式3 - 单例模式【Singleton Pattern】
设计模式3 - 单例模式【Singleton Pattern】
34 0
|
设计模式 安全 Java
Java单例模式(Singleton Pattern)
Java单例模式(Singleton Pattern)
|
安全 Java
单例模式(Singleton Pattern)(三)
上篇文章我们讲述了,单例的推荐使用方式,以及反射对单例的破坏,但文末留了一个疑问,就是序列化如何破坏一个单例,那么本篇文章就来分析一下。
79 2
单例模式(Singleton Pattern)(三)
|
设计模式 安全 Java
单例模式(Singleton Pattern)(一)
单例模式是Java中最简单的设计模式了,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
133 2
单例模式(Singleton Pattern)(一)
|
设计模式 存储 缓存
详解Java设计模式之单例模式(Singleton Pattern)
详解Java设计模式之单例模式(Singleton Pattern)
232 0
详解Java设计模式之单例模式(Singleton Pattern)
|
安全 Java
创建型 - 单例模式(Singleton pattern)
创建型 - 单例模式(Singleton pattern)
创建型 - 单例模式(Singleton pattern)
|
设计模式 安全 Java
创建型模式 - 单例模式(Singleton Pattern)
创建型模式 - 单例模式(Singleton Pattern)
|
设计模式 安全 调度
【Singleton Pattern】设计模式之单例模式
【Singleton Pattern】设计模式之单例模式
157 0
【Singleton Pattern】设计模式之单例模式