This is the 19th day of my participation in the Genwen Challenge

Singleton pattern two

We covered some of the singleton patterns in the last section, and we’ll move on to static inner classes and enumerations, as well as the singleton pattern in the source code.

Static inner class

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(a) {}private static class StaticinnerInstance {
        private static final Staticinnerclass INSTANCE = new Staticinnerclass();
    }

    public static Staticinnerclass getInstance(a) {
        returnStaticinnerInstance.INSTANCE; }}Copy the code

When a Staticinnerclass is loaded, the Staticinnerclass StaticinnerInstance is not loaded. It is initialized only when the code calls getInstance. This mode ensures both thread-safety and lazy loading. So it is also recommended.

The enumeration

package com.wangscaler.singleton;

/ * * *@author wangscaler
 * @date2021.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;
}
Copy the code

This approach not only avoids multi-threaded synchronization problems, but also prevents deserialization from recreating new objects, and is recommended. Speaking of deserialization, how does deserialization affect the singleton pattern?

Impact of deserialization on singleton patterns

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{
        // After privatization, external cannot be new
        private StatiConst(a) {}private final static StatiConst instance = new StatiConst();

        public static StatiConst getInstance(a) {
            returninstance; }}}Copy the code

The result after execution is

1452126962
325040804
false
Copy the code

As you can see from the above, after writing an object to a file, reading it from the file, deserializing it into an object, you get a hash that is not the same object as before. So how do we make the deserialized object the same as the original object? The singleton rule is guaranteed by adding the readResolve method, which is automatically called when the JVM deserializes a new object from memory and “assembles” it. The modified code

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{
        // After privatization, external cannot be new
        private StatiConst(a) {}private final static StatiConst instance = new StatiConst();

        public static StatiConst getInstance(a) {
            return instance;
        }

        private Object readResolve(a){
            returninstance; }}}Copy the code

The execution result

325040804
325040804
true
Copy the code

As you can see, by adding this method, we have achieved our desired effect, so when does the program execute this code? Open source we found

/** 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(a)
    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;
    }
Copy the code

In our program, after calling inputStream.readObject(), it checks if there is a readObjectOverride(), and if so, it replaces the readObject, which it doesn’t. Object obj = readObject0(false); So after we execute this code and go into readObject0, because we’re objects, ReadOrdinaryObject (unshared) in case TC_OBJECT obj = desc.isinstantiable ()? desc.newInstance() : null; First determine whether the object can be instantiated, if so, instantiate, and then

if(obj ! =null &&
    handles.lookupException(passHandle) == null &&
    desc.hasReadResolveMethod())
Copy the code

By desc. HasReadResolveMethod () whether we wrote a ReadResolve method, if we write, call the desc. InvokeReadResolve (obj); To call the ReadResolve method we wrote. Open invokeReadResolve with the following source code

/** 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 newInternalError(ex); }}else {
        throw newUnsupportedOperationException(); }}Copy the code

After calling the method that we wrote, he’s going to determine if the object that we returned is the same object that he just generated

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);
}
Copy the code

If not, it assigns the object that we returned to the object that it generated earlier, so whether it was the object that we deserialized or the object that it generated earlier. So why do enumerators get this object without writing this method

/** * Reads in and returns enum constant, or null if enum type is * unresolvable. Sets passHandle to enum constant's assigned handle. */
privateEnum<? > 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;
}
Copy the code

Enum. ValueOf ((Class)cl, name); , fetch the object by name, and result = en; Assign to the previous object.

Singleton schema in the JDK

Take the Runtime in the JDK as an example

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(a) {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime(a) {}}Copy the code

You can see that the source code for our Runtime is the hunchman type used in the singleton pattern.

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)
     * @since1.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(a) {
        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(a){
        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);
        }

        returndesktop; }}Copy the code

The Desktop here is a singleton pattern of enumeration types used.

conclusion

To sum up, in the singleton pattern, can be used as

  • Hungry (single thread, resulting in memory waste)

  • Double check lock

  • Static inner class

  • The enumeration

Usage: frequent object creation and destruction or time-consuming object creation with many resources (such as session factory, tool class….) The Runtime may be triggered frequently during development, greatly reducing resource usage.

Note: To use the singleton, you must use the provided get method instead of new objects, such as the Runtime getRuntime method.

Benefits: Saves resources and improves performance.

Disadvantages: If not used for a long time, it will be recycled, loss of state data; If too many programs use this object, it will cause overflows and is not suitable for objects that change frequently

The resources

  • JAVA Design Pattern