[Java design mode] IV. 4.1 creator mode - single example mode

Posted by fewtrem on Tue, 01 Mar 2022 04:30:55 +0100

The main focus of creative mode is "how to create objects?", Its main feature is "separating the creation and use of objects".

This can reduce the coupling degree of the system, and users do not need to pay attention to the creation details of objects.

The creation mode is divided into:

  • Singleton mode
  • Factory method model
  • Abstract engineering pattern
  • Prototype mode
  • Builder pattern

4.1 single case design mode

Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern is a creation pattern, which provides the best way to create objects.

This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating the object of this class.

4.1.1 structure of singleton mode

The main roles of singleton mode are as follows:

  • Singleton class. Only one instance of a class can be created
  • Access class. Using singleton classes

4.1.2 implementation of singleton mode

There are two types of singleton design patterns:

Hungry Chinese style: class loading will cause the single instance object to be created

Lazy: class loading does not cause the single instance object to be created, but only when the object is used for the first time

  1. Hungry Han style - mode 1 (static variable mode)

    /**
     * Hungry Han style
     *      Static variables create objects of classes
     */
    public class Singleton {
        //Private construction method
        private Singleton() {}
    
        //Create an object of this class at the member location
        private static Singleton instance = new Singleton();
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
            return instance;
        }
    }
    

    explain:

    This method declares the static variable of Singleton type at the member position, and creates the object instance of Singleton class. The instance object is created as the class loads. If the object is large enough and has not been used, it will cause a waste of memory.

  2. Hungry Chinese style - mode 2 (static code block mode)

    /**
     * Hungry Han style
     *      Create this type of object in a static code block
     */
    public class Singleton {
    
        //Private construction method
        private Singleton() {}
    
        //Create an object of this class at the member location
        private static Singleton instance;
    
        static {
            instance = new Singleton();
        }
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
            return instance;
        }
    }
    

    explain:

    In this way, a static variable of Singleton type is declared at the member position, and the object is created in the static code block and for the loading of the class. Therefore, it is basically the same as hungry man mode 1. Of course, this mode also has the problem of memory waste.

  3. Lazy - mode 1 (thread unsafe)

    /**
     * Lazy style
     *  Thread unsafe
     */
    public class Singleton {
        //Private construction method
        private Singleton() {}
    
        //Create an object of this class at the member location
        private static Singleton instance;
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
    
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    explain:

    From the above code, we can see that this method declares a static variable of Singleton type at the member position, and does not assign an object. When did the assignment take place? The Singleton class object is created only when the getInstance() method is called to obtain the Singleton class object, which realizes the effect of lazy loading. However, if it is a multithreaded environment, thread safety problems will occur.

  4. Lazy - mode 2 (thread safe)

    /**
     * Lazy style
     *  Thread safety
     */
    public class Singleton {
        //Private construction method
        private Singleton() {}
    
        //Create an object of this class at the member location
        private static Singleton instance;
    
        //Provide a static method to get the object
        public static synchronized Singleton getInstance() {
    
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    explain:

    This method also realizes the effect of lazy loading and solves the problem of thread safety. However, the synchronized keyword is added to the getInstance() method, resulting in a particularly low execution effect of this method. From the above code, we can see that the thread safety problem only occurs when initializing instance. Once the initialization is completed, it does not exist.

  5. Lazy - mode 3 (double check lock)

    Let's talk about locking in lazy mode. For getInstance() method, most operations are read operations, which are thread safe. Therefore, we don't have to make each thread hold a lock to call this method. We need to adjust the timing of locking. This also produces a new implementation mode: double check lock mode

    /**
     * Double check mode
     */
    public class Singleton { 
    
        //Private construction method
        private Singleton() {}
    
        private static Singleton instance;
    
       //Provide a static method to get the object
        public static Singleton getInstance() {
    		//For the first time, if instance is not null, it will not enter the lock grabbing stage and return the instance directly
            if(instance == null) {
                synchronized (Singleton.class) {
                    //After grabbing the lock, judge whether it is null again
                    if(instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    Double check lock mode is a very good singleton implementation mode, which solves the problems of singleton, performance and thread safety. The above double check lock mode looks perfect, but it is actually a problem. In the case of multithreading, null pointer problems may occur. The reason for the problem is that the JVM will optimize and reorder instructions when instantiating objects.

    To solve the problem of null pointer exception caused by double check lock mode, you only need to use volatile keyword, which can ensure visibility and order.

    /**
     * Double check mode
     */
    public class Singleton {
    
        //Private construction method
        private Singleton() {}
    
        private static volatile Singleton instance;
    
       //Provide a static method to get the object
        public static Singleton getInstance() {
    		//In the first judgment, if instance is not null, it will not enter the lock grabbing stage and return to the actual state directly
            if(instance == null) {
                synchronized (Singleton.class) {
                    //After grabbing the lock, judge whether it is empty again
                    if(instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    Summary:

    The double check lock mode after adding volatile keyword is a better singleton implementation mode, which can ensure thread safety and no performance problems in the case of multithreading.

    For more multithreading issues: Thread synchronization - double check lock implementation single instance mode

  6. Lazy - mode 4 (static inner class mode)

    In the static internal class singleton mode, the instance is created by the internal class. Because the JVM will not load the static internal class in the process of loading the external class, it will be loaded only when the properties / methods of the internal class are called, and its static properties will be initialized. Because static attributes are modified by static, they are guaranteed to be instantiated only once, and the instantiation order is strictly guaranteed.

    /**
     * Static internal class mode
     */
    public class Singleton {
    
        //Private construction method
        private Singleton() {}
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    explain:

    The INSTANCE will not be initialized when the Singleton class is loaded for the first time. Only getInstance is called for the first time. The virtual machine loads the SingletonHolder and initializes the INSTANCE, which can not only ensure thread safety, but also ensure the uniqueness of the Singleton class.

    Summary:

    Static inner class singleton mode is an excellent singleton mode, which is commonly used in open source projects. Without any lock, it ensures the safety of multithreading without any performance impact and waste of space.

  7. Enumeration mode

    Enumeration class implementation singleton mode is the highly recommended singleton implementation mode, because the enumeration type is thread safe and can only be loaded once. The designer makes full use of this feature of enumeration to realize the singleton mode. The writing method of enumeration is very simple, and the enumeration type is the only singleton implementation mode that will not be destroyed.

    /**
     * Enumeration mode
     */
    public enum Singleton {
        INSTANCE;
    }
    

    explain:

    The enumeration method belongs to the hungry Chinese method.

4.1.3 existing problems

4.1.3.1 problem demonstration

Destroy singleton mode:

Enables the Singleton class defined above to create multiple objects, except enumeration. There are two ways, serialization and reflection.

  • Serialization and deserialization

    Singleton class:

    public class Singleton implements Serializable {
    
        //Private construction method
        private Singleton() {}
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    Test class:

    public class Test {
        public static void main(String[] args) throws Exception {
            //Write objects to files
            //writeObject2File();
            //Read object from file
            Singleton s1 = readObjectFromFile();
            Singleton s2 = readObjectFromFile();
    
            //Judge whether two deserialized objects are the same object
            System.out.println(s1 == s2);
        }
    
        private static Singleton readObjectFromFile() throws Exception {
            //Create object input stream object
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            //First read Singleton object
            Singleton instance = (Singleton) ois.readObject();
    
            return instance;
        }
    
        public static void writeObject2File() throws Exception {
            //Gets the object of the Singleton class
            Singleton instance = Singleton.getInstance();
            //Create object output stream
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            //Write out the instance object to a file
            oos.writeObject(instance);
        }
    }
    

    The running result of the above code is false, indicating that serialization and deserialization have broken the singleton design pattern.

  • reflex

    Singleton class:

    public class Singleton {
    
        //Private construction method
        private Singleton() {}
        
        private static volatile Singleton instance;
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
    
            if(instance != null) {
                return instance;
            }
    
            synchronized (Singleton.class) {
                if(instance != null) {
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    Test class:

    public class Test {
        public static void main(String[] args) throws Exception {
            //Gets the bytecode object of the Singleton class
            Class clazz = Singleton.class;
            //Gets the private parameterless constructor object of the Singleton class
            Constructor constructor = clazz.getDeclaredConstructor();
            //Cancel access check
            constructor.setAccessible(true);
    
            //Create object s1 of Singleton class
            Singleton s1 = (Singleton) constructor.newInstance();
            //Create object s2 of Singleton class
            Singleton s2 = (Singleton) constructor.newInstance();
    
            //Judge whether two Singleton objects created by reflection are the same object
            System.out.println(s1 == s2);
        }
    }
    

    The running result of the above code is false, indicating that serialization and deserialization have broken the singleton design pattern

Note: the enumeration method will not cause these two problems.

4.1.3.2 problem solving

  • Solution of destroying singleton pattern by serialization and deserialization

    Add the readResolve() method to the Singleton class. It is called by reflection during deserialization. If this method is defined, it returns the value of this method. If it is not defined, it returns the new object.

    Singleton class:

    public class Singleton implements Serializable {
    
        //Private construction method
        private Singleton() {}
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
        
        /**
         * The following is to solve the serialization and deserialization cracking singleton mode
         */
        private Object readResolve() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    Source code analysis:

    ObjectInputStream class

    public final Object readObject() throws IOException, ClassNotFoundException{
        ...
        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);//Focus on the readObject0 method
        .....
    }
        
    private Object readObject0(boolean unshared) throws IOException {
    	...
        try {
    		switch (tc) {
    			...
    			case TC_OBJECT:
    				return checkResolve(readOrdinaryObject(unshared));//Focus on the readOrdinaryObject method
    			...
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }    
    }
        
    private Object readOrdinaryObject(boolean unshared) throws IOException {
    	...
    	//Isinstantable returns true and executes desc.newInstance() to create a new singleton class through reflection,
        obj = desc.isInstantiable() ? desc.newInstance() : null; 
        ...
        // After adding readResolve method in Singleton class, the execution result of desc.hasReadResolveMethod() method is true
        if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
        	// Call the readResolve method in the Singleton class through reflection and assign the return value to the rep variable
        	// In this way, the readObject method in the ObjectInputStream class will be called many times, and then the readResolve method defined by us will be called, so the same object will be returned.
        	Object rep = desc.invokeReadResolve(obj);
         	...
        }
        return obj;
    }
    
  • Solution of cracking single case in reflection mode

    public class Singleton {
    
        //Private construction method
        private Singleton() {
            /*
               Code to be added for reflection cracking singleton mode
            */
            if(instance != null) {
                throw new RuntimeException();
            }
        }
        
        private static volatile Singleton instance;
    
        //Provide a static method to get the object
        public static Singleton getInstance() {
    
            if(instance != null) {
                return instance;
            }
    
            synchronized (Singleton.class) {
                if(instance != null) {
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    explain:

    This way is easier to understand. When the constructor is called for creation through reflection, an exception is thrown directly. Do not run this operation.

4.1.4 JDK source code analysis - Runtime class

The Runtime class is the singleton design pattern used.

  1. See which singleton mode is used through the source code

    public class Runtime {
        private static Runtime currentRuntime = 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() {}
        ...
    }
    

    From the above source code, we can see that the Runtime class uses the evil Chinese style (static attribute) to implement the singleton mode.

  2. Using methods in the Runtime class

    public class RuntimeDemo {
        public static void main(String[] args) throws IOException {
            //Get Runtime class object
            Runtime runtime = Runtime.getRuntime();
    
            //Returns the total amount of memory in the Java virtual machine.
            System.out.println(runtime.totalMemory());
            //Returns the maximum amount of memory that the Java virtual machine is trying to use.
            System.out.println(runtime.maxMemory());
    
            //Create a new process, execute the specified string command, and return the process object
            Process process = runtime.exec("ipconfig");
            //Get the result after the command is executed, and get it through the input stream
            InputStream inputStream = process.getInputStream();
            byte[] arr = new byte[1024 * 1024* 100];
            int b = inputStream.read(arr);
            System.out.println(new String(arr,0,b,"gbk"));
        }
    }
    

Topics: Java Algorithm Singleton pattern