Design pattern - singleton pattern

Posted by kkeim on Tue, 22 Feb 2022 14:53:05 +0100

introduce

Singleton pattern is the most commonly used and simplest design pattern. The main use scenario is on various tool classes in program development. Its function is to ensure that there is only one instance of a class in the whole program.

example

We generally divide the single case mode into hungry man single case mode and lazy man mode.

Single case of hungry man

The hungry man singleton is automatically created during such loading. The examples are as follows

/**
 * @author : psyduck
 * @Date : 2022/2/18 3:10
 * @Desc :Single case model -- hungry versus lazy
 * Because the object is created at the beginning, the operation is faster, but it will exist for a longer time, occupy more resources, and it is easy to produce garbage objects
 * Initialize lazy load no
 * Multithreading safety is
 **/
public class SingletonModeHungry {

    // There is a private static instance of this type
    private static SingletonModeHungry singletonModeHungry = new SingletonModeHungry();

    /**
     * description : The singleton mode only provides private constructs < br / >
     * @author psyduck
     * @date 2022/2/20 11:01
     * @return
     */
    private SingletonModeHungry(){

    }

    /**
     * description : Return singletonMode object through public method < br / >
     * @param  
     * @author psyduck
     * @date 2022/2/20 11:10
     * @return DesignMode.SingletonMode
     */
    public static SingletonModeHungry getSingletonMode(){
        return singletonModeHungry;
    }
}

As can be seen from this example
1. This class cannot generate an instance through new because a private construct is created manually
2. This class manually creates a static object of this class
3. Private static objects can only be accessed through exposed public static methods
This is also the basic feature of singleton mode

In addition, the advantages and disadvantages of the hungry man model itself are as follows

advantage

1. The operation speed is faster, because the object is created directly when the class is loaded, so there is no need to waste the time of creating the object when it is first used
2. Multithreading security

shortcoming

1. Because the object is created when the class is loaded, the object exists longer than it needs, and takes up more memory and resources
2. Objects that are easy to generate garbage and objects that have not been used from beginning to end

Lazy single case

/**
 * @author : psyduck
 * @Date : 2022/2/20 11:26
 * @Desc : Singleton lazy
 * The object is initialized only after the first call, avoiding memory waste
 * Lazy loading is
 * Thread safe no
 * However, because this method should not be called many times in the whole program, it has little impact on efficiency
 **/
public class SingletonModeLazy {
    // Static private object
    private static SingletonModeLazy singletonModeLazy;
    // Static construction
    private SingletonModeLazy(){

    }
    //Determine whether this method is called for the first time. If yes, create an object
    public static SingletonModeLazy getSingletonModeLazy(){
        if(singletonModeLazy == null){
            singletonModeLazy = new SingletonModeLazy();
        }
        return singletonModeLazy;
    }
}

Lazy mode, like hungry mode, is also a kind of singleton mode, but it is different from hungry mode. Lazy mode only generates objects when they are called for the first time

The advantages and disadvantages of lazy mode are as follows

advantage

1. Compared with hungry man mode, memory waste is avoided

shortcoming

1. It is not safe in the case of multithreading. There may be multiple objects
... and other questions will be raised one by one in the following examples

Detailed explanation of the problem

Although the lazy mode has more lazy loading than the hungry mode and avoids the waste of memory, this form also leads to the execution of new SingletonModeLazy many times in the case of multithreading because the getSingletonModeLazy method is called at the same time, which makes the object not unique. The singleton mode is to ensure that there is and only one instance of this kind in the whole program, Therefore, some improvements are needed

Improvement of lazy man single example -- synchronous lock

The first solution most people should be able to come up with is to add the synchronized keyword to the get method to make the method a synchronous method. Naturally, multithreading concurrency will not lead to the existence of multiple objects. An example is as follows

/**
 * @author : psyduck
 * @Date : 2022/2/21 16:20
 * @Desc :The first method to solve thread safety -- method synchronization lock
 * Adding synchronized synchronization locks to get methods will sacrifice efficiency
 **/
public class SingletonModeLazyThreadSafeOne {

    private static SingletonModeLazyThreadSafeOne singletonModeLazyThreadSafeOne;

    private SingletonModeLazyThreadSafeOne(){

    }

    public static synchronized SingletonModeLazyThreadSafeOne getSingletonModeLazyThreadSafeOne(){
        if(singletonModeLazyThreadSafeOne == null){
            singletonModeLazyThreadSafeOne = new SingletonModeLazyThreadSafeOne();
        }
        return singletonModeLazyThreadSafeOne;
    }
}

So what are the advantages and disadvantages of this example compared with the previous example of lazy mode?

advantage

1. Solve the problem of multiple objects in multithreading

shortcoming

1. Synchronizing the whole method will greatly affect the performance. For example, if this method takes 0.1s and 100 threads are calling this method at the same time, that is, 99 of them enter the blocking state, the overall waste is 9.9s, so it still needs to be improved at this time
... other questions will be raised in the following examples

Improvement of lazy single case -- double check lock

In order to optimize the performance of the previous example, we need to reduce the amount of synchronized code, so we have the writing method of double check lock. The example is as follows

/**
 * @author : psyduck
 * @Date : 2022/2/21 16:53
 * @Desc : The third solution to the lazy thread safety problem -- double check lock
 **/
public class SingletonModeLazyThreadSafeTwo {
    private static SingletonModeLazyThreadSafeTwo singleton;

    private SingletonModeLazyThreadSafeTwo(){

    }
    
    // When using double check to avoid thread insecurity in lazy mode, there is a multi-threaded reordering problem
    // Namely
    // Set singleton = new SingletonModeLazyThreadSafeTwo(); Disassembly consists of three steps
    // 1. Allocate memory space 2 Initialize object 3 Set singleton to point to the corresponding memory space
    // However, the order of these three steps may not be fixed, which is in line with the Java specification
    // When the order is changed to 132, a singleton will appear when another thread accesses this method after the third step is executed= In the case of null, the returned object is an uninitialized object
    public static SingletonModeLazyThreadSafeTwo getSingleton(){
        if(singleton == null){
            // Because the get method is a static method, class objects that are not static or instantiated cannot be used inside it
            // So we usually synchronize the class itself directly
            synchronized (SingletonModeLazyThreadSafeTwo.class){
                if (singleton == null){
                    singleton = new SingletonModeLazyThreadSafeTwo();
                }
            }
        }
        return singleton;
    }
}

The advantages and disadvantages of this example compared with the previous example are as follows

advantage

1. The locked synchronous code block takes little time and improves the performance

shortcoming

1. Because synchronizing code blocks takes less time, there is a reordering problem

Detailed explanation of the problem

In short, after thread A executes the code, after the new singletonmodelazythreadsafetwomethod,
The Java virtual opportunity performs three-step operations to actually create objects. In this case, due to the extremely low time-consuming synchronization code block, when the three-step operation is not finished, thread B also performs the same operation. At this time, the object creation of thread A is not finished, so thread B obtains an object that has not been initialized. The actual three-step operation diagram is as follows

Therefore, for this problem, we still need to optimize the code

Improvement of lazy single case -- volatile + double check lock

Examples are as follows

/**
 * @author : psyduck
 * @Date : 2022/2/21 16:53
 * @Desc : The third solution to the lazy thread safety problem -- double check lock
 **/
public class SingletonModeLazyThreadSafeTwo {
    // In the case of volatile, there is no reordering
    private volatile static SingletonModeLazyThreadSafeTwo singleton;

    private SingletonModeLazyThreadSafeTwo(){

    }

    public static void main(String[] args) throws Exception{
        // First, get a class object
        SingletonModeLazyThreadSafeTwo singleton = SingletonModeLazyThreadSafeTwo.getSingleton();
        // All constructors of this class are then acquired by reflection
        Constructor<SingletonModeLazyThreadSafeTwo> constructor = SingletonModeLazyThreadSafeTwo.class.getDeclaredConstructor();
        // Then set the constructor to access the private variable / suppress the security check of the corresponding class
        constructor.setAccessible(true);
        // Finally, the constructor is used to directly call the private construction of this class to achieve the purpose of creating a new object
        SingletonModeLazyThreadSafeTwo newSingleton = constructor.newInstance();
        // Comparing the new object with the old object, we can find that the memory address is different
        System.out.println(singleton);
        System.out.println(newSingleton);
        System.out.println(singleton == newSingleton);
    }
    // When using double check to avoid thread insecurity in lazy mode, there is a multi-threaded reordering problem
    // Namely
    // Set singleton = new SingletonModeLazyThreadSafeTwo(); Disassembly consists of three steps
    // 1. Allocate memory space 2 Initialize object 3 Set singleton to point to the corresponding memory space
    // However, the order of these three steps may not be fixed, which is in line with the Java specification
    // When the order is changed to 132, a singleton will appear when another thread accesses this method after the third step is executed= In the case of null, the returned object is an uninitialized object
    public static SingletonModeLazyThreadSafeTwo getSingleton(){
        if(singleton == null){
            // Because the get method is a static method, class objects that are not static or instantiated cannot be used inside it
            synchronized (SingletonModeLazyThreadSafeTwo.class){
                if (singleton == null){
                    singleton = new SingletonModeLazyThreadSafeTwo();
                }
            }
        }
        return singleton;
    }
}

After adding the volatile keyword, the creation steps of the JVM virtual machine for this object will be forced to be executed in order, so that there will be no reordering problem
In addition, there is another solution to the reordering problem, as follows

Lazy singleton improvement -- static inner class

/**
 * @author : psyduck
 * @Date : 2022/2/21 16:23
 * @Desc :The second solution to the thread safety problem in the case of lazy in singleton mode -- static inner class
 * Using static internal classes to generate class objects and obtain them externally isolates the problems caused by multithreading, because the Java virtual machine only allows one thread to perform class initialization and loading at the same time
 * It also conforms to the specification of lazy mode. This class will be loaded only when it is used for the first time
 **/
public class SingletonModeLazyThreadSafeThree {

    private static class Singleton{
        private static SingletonModeLazyThreadSafeThree singleton = new SingletonModeLazyThreadSafeThree();
    }

    private SingletonModeLazyThreadSafeThree(){

    }

    public static SingletonModeLazyThreadSafeThree getSingleton(){
        return Singleton.singleton;
    }
}

The implementation of this singleton may be slightly different from the previous examples, but it also meets the requirements of the singleton mode. At the same time, because the object is in the anonymous inner class, when we use the get method to obtain the corresponding object, the anonymous inner class (the anonymous inner class will start class loading only when it is used) will start class loading, At the same time, the Java virtual machine will only let one thread perform class initialization and force other threads to enter blocking. In this way, although the object directly performs new operation, it still plays the role of lazy loading and is thread safe.
However, the above two thread safe singletons still have other problems, as follows

Reflection and deserialization attacks in singleton mode

Topics: Java Design Pattern Singleton pattern