Singleton mode of design mode (instruction rearrangement, DCL)

The singleton mode has always been considered the simplest creation mode in the design mode, and it is also the easiest mode to write. But there are many detailed questions in it, and they are often asked in interviews. As the name implies, in this mode, when the entire system environment is running, there is only one instance, and this instance is not created by other objects outside the object, but it is responsible for creating its own object and providing access to this unique instance. .

The most common singleton mode is the MySQL issuer (controlling the global generation of a unique ID), the Redis connection object, and the common Windows task manager to view the process. These are common places for the singleton mode. Its implementation is also very simple, the main core is - privatizing the constructor and providing a method to obtain an instance. According to the creation period of the instance, the singleton design pattern is also divided into two types - the lazy pattern and the hungry pattern. The following will expand on these two patterns in detail.

lazy mode

The so-called lazy man refers to the creation of objects when they need to be used, which can reduce the memory usage to a certain extent. To implement a single instance of lazy man mode, according to the implementation core mentioned above, it is the following code.

/**
 * @author SunRains
 * @date 2022-02-26 16:18
 **/
public class SingleLazy {
    private static SingleLazy instance;

    private SingleLazy() {
    }

    private void doSomething(){
        System.out.println("Do something...");
    }

    public static SingleLazy getInstance() {
        if (instance == null) {
            instance = new SingleLazy();
        }
        return instance;
    }
}

This is the most basic implementation of singletons, and it is also written in many books. But after a deeper understanding, I found that this code is only suitable for single thread. In the case of multi-threading, it is possible that thread A and B did not instantiate the instance when calling getInstance, thus instantiating it twice, which is contrary to the singleton mode thinking. Therefore, in multi-threaded mode, this method is not recommended, because it is easy to cause thread insecurity.

So since the thread is not safe, there must be a better way. You may think that if only the getInstance method can only be called by one thread at the same time, that is, if the method can be modified with the synchronized keyword, the answer is yes.

 public static synchronized SingleLazy getInstance() {
        if (instance == null) {
            instance = new SingleLazy();
        }
        return instance;
    }

Although this method can solve the problem of thread safety, the efficiency is the worst. First of all, adding synchronized to the entire method will make the lock granularity very large and cause a lot of overhead. Secondly, think about our ultimate goal just to control the instantiated code in a synchronous operation, that is, to create an instantiated object only when it is called for the first time.

double check DCL

So with further optimization, double check locks are introduced. The so-called double check lock is to check the instance for null inside and outside the lock respectively. The reason for making two judgments is that two threads may execute the outer statement of the synchronization code block at the same time. After thread one executes the initialization in the synchronization code block, thread two will also perform initialization, so it is necessary to execute it in the synchronization block. A check for null, so that thread two will not perform any operations.

public static SingleLazy getInstance() {
        if (instance == null) { // 一重校验
            synchronized (SingleLazy.class) {
                if (instance == null) { // 二重校验
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }

command rearrangement

This operation actually looks perfect. Just when we thought it was ok, there is a problem that we have been ignoring—instance = new SingleLazy(). You may be wondering, why is there a problem with this code? If you don't understand the JVM virtual machine, you really can't figure this out. Because this statement seems to be an atomic operation, but it is not. Inside the JVM, this passage mainly performs three things.

① Allocate space to the instance object.

② Instantiate the object in the space.

③ Copy instance to reference instance.

There is an optimization of instruction reordering in the JVM compiler, which means that the above execution order may be 1-2-3 or 1-3-2. If the execution order of threads is 1=》3=》2 , the value will be written into the memory first, but it has not been initialized yet. At this time, the instance is non-null and not a complete object. Therefore, if thread 2 preempts the execution, it will directly return the instance, and then use it directly, so the problem will arise.

All that needs to be done at this point is to declare the instance variable as volatile. Volatile is a keyword provided by Java. It has visibility and order, that is, instruction reordering optimization is prohibited. Instruction reordering is the optimization of statement execution by the JVM. As long as there is no dependency between statements, the JVM will optimize the statement.

/**
 * @author SunRains
 * @date 2022-02-26 16:43
 **/
public class SingleLazy {
    private static volatile SingleLazy instance;

    private SingleLazy() {
    }

    private void doSomething() {
        System.out.println("Do something...");
    }

    public static SingleLazy getInstance() {
        if (instance == null) {
            synchronized (SingleLazy.class) {
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }
}

But pay special attention to the fact that there is a problem with the double check of Volatile in versions before Java 5, because the Java memory model before Java 5 is flawed, that is, declaring variables as volatile cannot completely avoid reordering.

hungry man mode

This method is very simple, because the singleton instance is declared as static and final variables, and will be initialized when the class is loaded into memory for the first time.

/**
 * @author SunRains
 * @date 2022-02-28 9:26
 **/
public class SingleHungry {

    private final static SingleHungry instance=new SingleHungry();

    private SingleHungry(){
    }

    public static SingleHungry getInstance(){
        return instance;
    }
}

 This way of writing does not need to consider the problem of double verification lock, but it will be initialized when the class is loaded, even if the client does not call the getInstance method. In this way, the creation of some singleton instances depends on parameters or configuration files. Before getInstance(), a method must be called to set parameters for it, so this singleton writing method cannot be used.

Guess you like

Origin blog.csdn.net/qq_35363507/article/details/123097090