设计模式六–单例模式

这是我参与更文挑战的第19天,活动详情查看: 更文挑战

单例模式二

上节我们讲了一部分单例模式,接下来继续讲解静态内部类和枚举,以及源码中的单例模式。

静态内部类

package com.wangscaler.singleton;

/**
 * @author wangscaler
 * @date 2021.06.21 17:42
 */
public class Staticinnerclass {
    public static void main(String[] args) {
        Staticinnerclass staticinnerclass = Staticinnerclass.getInstance();
        Staticinnerclass staticinnerclass1 = Staticinnerclass.getInstance();
        System.out.println(staticinnerclass.hashCode());
        System.out.println(staticinnerclass1.hashCode());
        System.out.println(staticinnerclass == staticinnerclass1);
    }

    private Staticinnerclass() {
    }

    private static class StaticinnerInstance {
        private static final Staticinnerclass INSTANCE = new Staticinnerclass();
    }

    public static Staticinnerclass getInstance() {
        return StaticinnerInstance.INSTANCE;
    }
}
复制代码

Staticinnerclass装载的时候,静态内部类StaticinnerInstance是不会装载的,只有代码调用 getInstance时,调用他,才会被初始化。这种模式既能保证线程安全又能保证懒加载。所以也推荐使用。

枚举

package com.wangscaler.singleton;

/**
 * @author wangscaler
 * @date 2021.06.21 17:59
 */
public class Enumeration {
    public static void main(String[] args) {
        EnumerationSingleton enumerationSingleton = EnumerationSingleton.INSTANCE;
        EnumerationSingleton enumerationSingleton1 = EnumerationSingleton.INSTANCE;
        System.out.println(enumerationSingleton.hashCode());
        System.out.println(enumerationSingleton1.hashCode());
        System.out.println(enumerationSingleton == enumerationSingleton1);
    }
}

enum EnumerationSingleton {
    INSTANCE;
}
复制代码

此种方式,不仅避免多线程同步问题,还防止反序列化重新创建新的对象,也是推荐使用的。
提到反序列化,那么反序列化时,会对单例模式造成什么影响呢?

反序列化对单例模式的影响

package com.wangscaler.singleton;

import java.io.*;

/**
 * @author wangscaler
 * @date 2021.06.18 11:18
 */

public class Hungryman {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        StatiConst statiConst1 = StatiConst.getInstance();
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization"));
        outputStream.writeObject(statiConst1);
        File file = new File("SingletonDeserialization");
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
        StatiConst statiConst = (StatiConst) inputStream.readObject();
        System.out.println(statiConst.hashCode());
        System.out.println(statiConst1.hashCode());
        System.out.println(statiConst == statiConst1);
    }

    static class StatiConst implements Serializable{
        //私有化之后,外部不可new
        private StatiConst() {
        }

        private final static StatiConst instance = new StatiConst();

        public static StatiConst getInstance() {
            return instance;
        }
        
    }
}
复制代码

执行之后的结果为

1452126962
325040804
false
复制代码

从上述可以看出,将对象写入文件中,再从文件读出来,反序列化成对象之后,拿到的hash并不是之前的对象了。那么我们怎么让反序列化之后的对象还是之前的对象呢?只需要增加方法readResolve,当JVM从内存中反序列化地”组装”一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 从而使得单例规则得到了保证。修改之后的代码

package com.wangscaler.singleton;

import java.io.*;

/**
 * @author wangscaler
 * @date 2021.06.18 11:18
 */

public class Hungryman {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        StatiConst statiConst1 = StatiConst.getInstance();
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonDeserialization"));
        outputStream.writeObject(statiConst1);
        File file = new File("SingletonDeserialization");
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
        StatiConst statiConst = (StatiConst) inputStream.readObject();
        System.out.println(statiConst.hashCode());
        System.out.println(statiConst1.hashCode());
        System.out.println(statiConst == statiConst1);
    }

    static class StatiConst implements Serializable{
        //私有化之后,外部不可new
        private StatiConst() {
        }

        private final static StatiConst instance = new StatiConst();

        public static StatiConst getInstance() {
            return instance;
        }

        private Object readResolve(){
            return instance;
        }
    }
}
复制代码

执行结果

325040804
325040804
true
复制代码

可以看到,我们增加了这个方法之后,就达到了我们的预期效果,那么程序什么时候执行了这段代码?打开源码我们发现

/** if true, invoke readObjectOverride() instead of readObject() */
    private final boolean enableOverride;
/**
 * Read an object from the ObjectInputStream.  The class of the object, the
 * signature of the class, and the values of the non-transient and
 * non-static fields of the class and all of its supertypes are read.
 * Default deserializing for a class can be overridden using the writeObject
 * and readObject methods.  Objects referenced by this object are read
 * transitively so that a complete equivalent graph of objects is
 * reconstructed by readObject.
 *
 * <p>The root object is completely restored when all of its fields and the
 * objects it references are completely restored.  At this point the object
 * validation callbacks are executed in order based on their registered
 * priorities. The callbacks are registered by objects (in the readObject
 * special methods) as they are individually restored.
 *
 * <p>Exceptions are thrown for problems with the InputStream and for
 * classes that should not be deserialized.  All exceptions are fatal to
 * the InputStream and leave it in an indeterminate state; it is up to the
 * caller to ignore or recover the stream state.
 *
 * @throws  ClassNotFoundException Class of a serialized object cannot be
 *          found.
 * @throws  InvalidClassException Something is wrong with a class used by
 *          serialization.
 * @throws  StreamCorruptedException Control information in the
 *          stream is inconsistent.
 * @throws  OptionalDataException Primitive data was found in the
 *          stream instead of objects.
 * @throws  IOException Any of the usual Input/Output related exceptions.
 */
public final Object readObject()
    throws IOException, ClassNotFoundException
{
    if (enableOverride) {
        return readObjectOverride();
    }

    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(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.
     */
    private Object readObject0(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:
                    return readHandle(unshared);

                case TC_CLASS:
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);

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

                case TC_ARRAY:
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    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);
        }
    }

/**
     * 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.
     */
    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;
    }
复制代码

在我们的程序中,调用inputStream.readObject()之后,他会先判断是否有readObjectOverride(),如果有则取代readObject,在这里是没有的,所以程序 往下执行Object obj = readObject0(false);执行完这句代码进入readObject0之后,因为我们是对象,所以会走case TC_OBJECT里的readOrdinaryObject(unshared)打开这个方法的源码我们发现有这样一句代码obj = desc.isInstantiable() ? desc.newInstance() : null;先判断该对象是否可以实例化,,如果可以则进行实例化,之后

if (obj != null &&
    handles.lookupException(passHandle) == null &&
    desc.hasReadResolveMethod())
复制代码

通过desc.hasReadResolveMethod()判断我们是否写了ReadResolve方法,如果我们写了,则调用desc.invokeReadResolve(obj);来调用我们写的ReadResolve方法。打开invokeReadResolve的源码如下

/** class-defined readResolve method, or null if none */
    private Method readResolveMethod;
/**
 * 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.
 */
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();
    }
}
复制代码

调用完我们写的方法之后,他会判断我们返回的对象,和他刚才生成的对象是否相同

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);
}
复制代码

如果不同,则将我们返回的对象,赋值给他之前生成的对象,从而是我们反序列化之后的对象还是之前的对象。那么为什么枚举类型的不要写这个方法就能拿到这个对象呢

/**
 * Reads in and returns enum constant, or null if enum type is
 * unresolvable.  Sets passHandle to enum constant's assigned handle.
 */
private Enum<?> readEnum(boolean unshared) throws IOException {
    if (bin.readByte() != TC_ENUM) {
        throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);
    if (!desc.isEnum()) {
        throw new InvalidClassException("non-enum class: " + desc);
    }

    int enumHandle = handles.assign(unshared ? unsharedMarker : null);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
        handles.markException(enumHandle, resolveEx);
    }

    String name = readString(false);
    Enum<?> result = null;
    Class<?> cl = desc.forClass();
    if (cl != null) {
        try {
            @SuppressWarnings("unchecked")
            Enum<?> en = Enum.valueOf((Class)cl, name);
            result = en;
        } catch (IllegalArgumentException ex) {
            throw (IOException) new InvalidObjectException(
                "enum constant " + name + " does not exist in " +
                cl).initCause(ex);
        }
        if (!unshared) {
            handles.setObject(enumHandle, result);
        }
    }

    handles.finish(enumHandle);
    passHandle = enumHandle;
    return result;
}
复制代码

我们可以在源码看到Enum.valueOf((Class)cl, name);,直接根据名字将对象取出来,然后result = en;赋值给之前的对象。

JDK中的单例模式

以JDK中的Runtime为例

public class Runtime {
    private static Runtime getRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
}
复制代码

可以看到我们Runtime的源码就是使用的单例模式中的饿汉式。

public class Desktop {

    /**
     * Represents an action type.  Each platform supports a different
     * set of actions.  You may use the {@link Desktop#isSupported}
     * method to determine if the given action is supported by the
     * current platform.
     * @see java.awt.Desktop#isSupported(java.awt.Desktop.Action)
     * @since 1.6
     */
    public static enum Action {
        /**
         * Represents an "open" action.
         * @see Desktop#open(java.io.File)
         */
        OPEN,
        /**
         * Represents an "edit" action.
         * @see Desktop#edit(java.io.File)
         */
        EDIT,
        /**
         * Represents a "print" action.
         * @see Desktop#print(java.io.File)
         */
        PRINT,
        /**
         * Represents a "mail" action.
         * @see Desktop#mail()
         * @see Desktop#mail(java.net.URI)
         */
        MAIL,
        /**
         * Represents a "browse" action.
         * @see Desktop#browse(java.net.URI)
         */
        BROWSE
    };

    private DesktopPeer peer;

    /**
     * Suppresses default constructor for noninstantiability.
     */
    private Desktop() {
        peer = Toolkit.getDefaultToolkit().createDesktopPeer(this);
    }

    /**
     * Returns the <code>Desktop</code> instance of the current
     * browser context.  On some platforms the Desktop API may not be
     * supported; use the {@link #isDesktopSupported} method to
     * determine if the current desktop is supported.
     * @return the Desktop instance of the current browser context
     * @throws HeadlessException if {@link
     * GraphicsEnvironment#isHeadless()} returns {@code true}
     * @throws UnsupportedOperationException if this class is not
     * supported on the current platform
     * @see #isDesktopSupported()
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static synchronized Desktop getDesktop(){
        if (GraphicsEnvironment.isHeadless()) throw new HeadlessException();
        if (!Desktop.isDesktopSupported()) {
            throw new UnsupportedOperationException("Desktop API is not " +
                                                    "supported on the current platform");
        }

        sun.awt.AppContext context = sun.awt.AppContext.getAppContext();
        Desktop desktop = (Desktop)context.get(Desktop.class);

        if (desktop == null) {
            desktop = new Desktop();
            context.put(Desktop.class, desktop);
        }

        return desktop;
    }
}
复制代码

这里的Desktop就是使用的枚举类型的单例模式。

总结

综上所述,单例模式中,可以使用的为

  • 饿汉式(单线程,会造成内存的浪费)

  • 双重检验锁

  • 静态内部类

  • 枚举

使用情况:需要频繁创建销毁对象或者创建对象时耗时长、资源多的情况(如session工厂,工具类….),Runtime在开发过程中,可能经常触发,大大减少了资源的占用。

注意情况:要想使用单例模式,必须使用提供的get方法,而不能去new对象,比如Runtime中使用getRuntime方法获取Runtime对象。

好处:节省资源,提高性能。

缺点:如果长时间不被使用,就会被回收,丢失状态数据;如果使用该对象的程序太多,会导致溢出,也不适合频繁变化的对象

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享