Implementation of singleton mode

Posted by bigphpn00b on Wed, 09 Feb 2022 07:54:21 +0100

1, What is singleton 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.

Note:
1. A singleton class can only have one instance.
2. A singleton class must create its own unique instance.
3. A singleton class must provide this instance to all other objects.

Implementation description:

1. Privatize the construction method of this class. The construction method is privatized. Other classes or codes cannot realize this class by calling the construction method of this class. They must be instantiated by calling the static method instantiation method provided by this class.
2. This class provides a static method to instantiate itself. If the class has been instantiated, it will directly provide the instantiated reference. If there is no instantiation, it will create an instantiated reference and give the reference maintained by the class.

The following is the structure diagram of the singleton mode:

2, Advantages and disadvantages of singleton mode
advantage:
1. Singleton mode can ensure that there is only one instance in memory and reduce the memory overhead.
2. Multiple occupation of resources can be avoided.
3. The global access point is set in the singleton mode, which can optimize and share the access of resources.
Disadvantages:
1. Singleton mode generally has no interface and is difficult to expand. If you want to expand, there is no second way except to modify the original code, which violates the opening and closing principle.
2. In concurrent testing, singleton mode is not conducive to code debugging. During debugging, if the code in the singleton is not executed, a new object cannot be simulated.
3. The function code of singleton mode is usually written in a class. If the function design is unreasonable, it is easy to violate the principle of single responsibility.

3, Application scenario of singleton mode

In the computer system, there are Windows recycle bin, file system in the operating system, thread pool in multithreading, driver object of graphics card, background processing service of printer, log object of application program, connection pool of database, counter of website, configuration object of Web application, dialog box in application program The cache in the system is often designed as a singleton.

Applicable scenarios:

1. The environment that needs to generate a unique sequence
2. Objects that need to be instantiated frequently and then destroyed.
3. Objects that take too much time or resources to create but are often used.
4. An environment that facilitates communication between resources

2, Multiple ways to realize single instance
1. Lazy mode
When the class is initialized, it will not be instantiated, and a reference will be instantiated only when necessary.

Method implementation:

/**
 * Lazy singleton mode
 */
public class LazySingleton {
    /**
     * Adding volatile ensures synchronization in the thread
     */
    private static  LazySingleton instance = null;
    /**
     * Privatized nonparametric structure
     */
    private LazySingleton(){
    }
    /**
     *  Adding synchronized is also for thread safety
     */
    public static  LazySingleton getInstance(){
        if(instance == null){
            instance = new LazySingleton();
        }
        return  instance;
    }
}

Test class:

public static void main(String[] args) throws Exception {
        //ptMethod();
        //fsMethod();
        threadDemo();
    }

    /**
     * Common method
     */
    public static void ptMethod(){
        System.out.println("Common method!");
        LazySingleton la1 = LazySingleton.getInstance();
        LazySingleton la2 = LazySingleton.getInstance();
        System.out.println(la1==la2);
    }
    /**
     * reflex
     */
    public static void fsMethod() throws Exception{
        System.out.println("Reflection method!");
        Class<LazySingleton> cl = LazySingleton.class;
        Constructor<LazySingleton> cla = cl.getDeclaredConstructor();
        cla.setAccessible(true);
        LazySingleton la1 = LazySingleton.getInstance();
        LazySingleton la2 = cla.newInstance();
        System.out.println(la1== la2);
    }

    /**
     * Multithreading
     */
    private static  void threadDemo(){
        System.out.println("Multithreading");
        for (int i = 0; i < 5; i++) {
            new Thread(){
                @Override
                public void run() {
                    System.out.println(LazySingleton.getInstance());
                }
            }.start();
        }
    }

Execution result:

General method:

Reflection method:

Multithreading method:

Conclusion:
As you can see from the above test code, the lazy mode is called thread safe in common methods, and the reflection call is not thread safe, and multithreading is also unsafe. The solution to thread insecurity is to add volatile when creating static object parameters, and add synchronized on static instantiation methods to achieve thread safety. (the above code has also been marked out)

2. Hungry man model
When the class is initialized, it will be instantiated first, but if it is not used all the time, it will occupy part of the memory.
Method implementation:

public class HungrySingleton {
    /**
     * Create entity class
     */
    private static HungrySingleton instance = new HungrySingleton();

    /**
     * Privatization construction method
     */
    private HungrySingleton(){}

    /**
     * Static external method
     * @return
     */
    public static HungrySingleton getInstance(){
        return instance;
    }
}

Test class:

public class Test2 {
    public static void main(String[] args) throws Exception{
        //ptMethod();
        //fsMethod();
        threadDemo();
    }
    /**
     * Common method
     */
    public static void ptMethod(){
        System.out.println("Common method!");
        HungrySingleton hs1 = HungrySingleton.getInstance();
        HungrySingleton hs2 = HungrySingleton.getInstance();
        System.out.println(hs1==hs2);
    }
    /**
     * reflex
     */
    public static void fsMethod() throws Exception{
        System.out.println("Reflection method!");
        Class<HungrySingleton> cl = HungrySingleton.class;
        Constructor<HungrySingleton> cla = cl.getDeclaredConstructor();
        cla.setAccessible(true);
        HungrySingleton hs1 = HungrySingleton.getInstance();
        HungrySingleton hs2 = cla.newInstance();
        System.out.println(hs1== hs2);
    }

    /**
     * Multithreading
     */
    private static  void threadDemo(){
        System.out.println("Multithreading");
        for (int i = 0; i < 5; i++) {
            new Thread(){
                @Override
                public void run() {
                    System.out.println(HungrySingleton.getInstance());
                }
            }.start();
        }
    }
}

Execution result:

General method:

Reflection method:

Thread method:

Summary:
Hungry Han mode ordinary call is thread safe, reflection call is not safe, thread call is safe, and does not need to lock, with high execution efficiency, but it is easy to produce garbage objects.
3. Registration mode

/**
 * Registration mode
 */
public class RegisterSingleton {

    private static class RegisterSingletonHodel{
        private static RegisterSingleton instance = new RegisterSingleton();
    }

    private RegisterSingleton(){}

    public static RegisterSingleton getInstance(){
        return RegisterSingletonHodel.instance;
    }

}

Test class:

public class Test3 {
    public static void main(String[] args) throws Exception{
        ptMethod();
        //fsMethod();
        //threadDemo();
    }
    /**
     * Common method
     */
    public static void ptMethod(){
        System.out.println("Common method!");
        RegisterSingleton rs1 = RegisterSingleton.getInstance();
        RegisterSingleton rs2 = RegisterSingleton.getInstance();
        System.out.println(rs1==rs2);
    }
    /**
     * reflex
     */
    public static void fsMethod() throws Exception{
        System.out.println("Reflection method!");
        Class<RegisterSingleton> cl = RegisterSingleton.class;
        Constructor<RegisterSingleton> cla = cl.getDeclaredConstructor();
        cla.setAccessible(true);
        RegisterSingleton rs1 = RegisterSingleton.getInstance();
        RegisterSingleton rs2 = cla.newInstance();
        System.out.println(rs1== rs2);
    }

    /**
     * Multithreading
     */
    private static  void threadDemo(){
        System.out.println("Multithreading");
        for (int i = 0; i < 5; i++) {
            new Thread(){
                @Override
                public void run() {
                    System.out.println(RegisterSingleton.getInstance());
                }
            }.start();
        }
    }
}

General method:

Reflection method:

Thread method:

Conclusion: the registration mode has the same effect as the lazy mode with double lock. It can delay loading and will not waste memory resources. Threads are also safe and reflection is not safe.
4. Enumeration mode

/**
 * Enumeration mode
 */
public enum EnumSingleton {
    INSTANCE;
    public void whateverMethod(){
        System.out.println("Enumeration mode");
    }

}

Test class:

public class Test4 {
    public static void main(String[] args) throws Exception{
        // ptMethod();
         fsMethod();
    }
    /**
     * Common method
     */
    public static void ptMethod(){
        System.out.println("Common method!");
        EnumSingleton es1 = EnumSingleton.INSTANCE;
        EnumSingleton es2 = EnumSingleton.INSTANCE;
        System.out.println(es1==es2);
    }
    /**
     * reflex
     */
    public static void fsMethod() throws Exception{
        System.out.println("Reflection method!");
        Class<EnumSingleton> cl = EnumSingleton.class;
        Constructor<EnumSingleton> cla = cl.getDeclaredConstructor();
        cla.setAccessible(true);
        EnumSingleton rs1 = EnumSingleton.INSTANCE;
        EnumSingleton rs2 = cla.newInstance();
        System.out.println(rs1== rs2);
    }

}

General method:

Reflection method:

Conclusion: the enumeration mode can not only avoid the problem of multi-threaded synchronization, but also automatically support the serialization mechanism to prevent deserialization and re create new objects. It absolutely prevents multiple instantiations, and cannot be called through reflection. The only disadvantage is that it cannot be inherited.

3, Final summary
Generally speaking, the lazy mode is not recommended. If there are special needs, you can choose to use the mode with double check lock. The hungry mode is recommended

The first and second lazy ways are not recommended, and the third hungry way is recommended. The fifth registration method can only be used when the lazy loading effect is to be clearly realized. If it involves deserialization to create objects, you can try to use the sixth enumeration method. If there are other special needs, the fourth double check lock mode can be considered

Topics: Java Design Pattern Interview Multithreading