What is singleton mode? Evolution of DCL model

What is singleton mode?

Singleton pattern is one of the simplest design patterns;

There is only one instance object in the program; The singleton mode does not require the user to create an object. The user can directly use the get() method to obtain the object. The class itself is responsible for creating objects, and only a single object is guaranteed to be created.

Writing method of Java singleton pattern

java There are several classic ways to write singleton mode
1.Hungry man model
2.Lazy mode
3.Double check mode

1. Immediate load / load mode

Immediate loading means that the object has been created when using the class. In immediate load / starve mode, the instance is created by the factory before the method is called.

private static final Singleton1 single = new Singleton1(); It can be seen that the statement is decorated with the static keyword, so the object has been initialized in the class loading phase, and the instance has been created by the factory before calling the getInstance() method.

And this method can ensure that there is only one instance. There is no ready-made security problem. The disadvantage is that memory space is wasted. Objects have been created whether they need to be used or not.

//Hungry Han style single instance class When the class is initialized, it has been instantiated by itself   
public class Singleton {  
    private Singleton() {}  
    private static final Singleton1 single = new Singleton1();  
    //Static factory method   
    public static Singleton1 getInstance() {  
        return single;  
    }  
}

2. Delayed loading / lazy mode

Delayed loading means that the instance is created by the factory only when the getInstance() method is called. Common methods instantiate new in the getInstance() method. In Chinese context, delayed loading means "slow" and "not urgent", so it is also called "lazy mode". In lazy loading / lazy mode, the method is invoked when it is created by the factory.

//Lazy singleton class Instantiate yourself on the first call   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //Static factory method   
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
} 

Disadvantages of lazy loading / lazy mode

In a multithreaded environment, lazy mode code has thread safety problems and can not guarantee singleton. Therefore, some measures should be taken to ensure thread safety, such as synchronization lock.

2.1 solution of delayed loading / starving mode: evolution process of DCL mode

Scenario 1: declare synchronized keyword

public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //The synchronized keyword is used, but the whole method is locked and inefficient
    synchronized public static Singleton getInstance() {  
        try{
           if (single == null) {    
               single = new Singleton();  
           }    
           return single;  
        }catch(InterrupedException e)
            e.printStackTrace();
        }
    }  
} 

Declaring the synchronized keyword makes the code synchronized, which solves the thread safety problem, but the whole method is locked and inefficient. Therefore, the following attempts to use the synchronized lock part of the code, do not lock the whole method, and try to improve efficiency.

2. Trying to synchronize code blocks

public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //This method is equivalent to synchronized public static Singleton getInstance(), which is inefficient
    public static Singleton getInstance() {  
        try{
           synchronized (Singleton.class){
               if (single == null) {    
                   single = new Singleton();  
               }    
               return single;  
           }
        }catch(InterrupedException e)
            e.printStackTrace();
        }
    }  
} 

This method is equivalent to synchronized public static Singleton getInstance(), which is inefficient. Therefore, the locked code is reduced again, and the important code is selected for separate synchronization. As follows:

3. Separate synchronization for some important code

public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //This method is equivalent to synchronized public static Singleton getInstance(), which is inefficient
    public static Singleton getInstance() {  
        try{
            if (single == null) {       //1. The judgment object is empty
                synchronized (Singleton.class){ //2. Synchronous lock
                single = new Singleton();  
                } 
            }
            return single;  
        }catch(InterrupedException e)
            e.printStackTrace();
        }
    }  
} 

The way synchronized locks important code is to achieve thread safety and improve efficiency. So there is the above code.

However, if you carefully consider the above code, you will find that the above code has thread safety problems:

When there are multiple threads, such as threads A and B, both threads go to 1 Judge the condition that the object is empty. Both threads are locked. At this time, only one thread can get the lock. For example, if thread A gets the lock, thread A goes down to create the object, and thread A releases the lock.

Then, when thread B gets the lock, it will also follow the logic of creating objects, which will lead to the situation of multiple instance objects. Therefore, after the object is judged empty for the first time, you need to judge that the object is empty again to create the object. As follows:

3.DCL Double Check Locking double check mechanism

DCL mechanism

public class SingletonDCL {
    private volatile static SingletonDCL singleton;
​
    public SingletonDCL() {
    }
​
    public static SingletonDCL getInstance() {
        if (singleton== null) {   //1. First check
            synchronized (Singleton.class) {
                if (singleton== null) {  //2. Why double check for the second check?
                    singleton= new SingletonDCL();
                }
            }
        }
        return singleton;
    }
}

Why double check- Second check

The code is as follows: the singleton== null object is judged not to be empty twice. Why do you judge not to be empty twice?

The code for check ing only once is as follows

1   public static SingletonDCL getInstance() {
2        if (singleton== null) {   //1. First check
3            synchronized (Singleton.class) {
4                    singleton= new SingletonDCL();
5            }
6        }
7        return singleton;
8    }

1. If a check is performed, multiple threads come in at the same time, and the threads are locked on the third line of code;

2. Only one thread can get the lock. If thread A gets the lock, it creates A new object, and then releases the lock;

3. When thread B gets the lock, it is behind the first checkif (singleton== null) and can create another object;

There will be thread safety issues. So you need to check again in the syncronized code block. This is Double Check

Why use volatile for single instance double lock?

Functions of using volatile:
Modify the variable singleton with volatile to make the variable visible among multiple threads. In addition, singleton = new Singleton() is prohibited; Code reordering.

Because singleton = new Singleton(); The code is executed internally in three steps:

1. memory = allocate; //Allocate memory space for objects
2. ctorInstance(memory); //Initialize object
3. singleton = memory; //Set instance to point to the memory address just allocated

If volatile is not used, the following problems will occur in the case of Concurrency: thread A executes to singleton= new SingletonDCL(); It is possible for the compiler to reorder these three steps into

1. memory = allocate; //Allocate memory space for objects
2. single = memory; //Set instance to point to the memory address just allocated
3. ctorInstance(memory); //Initialize object

The details are as follows:
When thread 1 executes, singleton= new SingletonDCL(); If an instruction rearrangement occurs, the execution order is 132. When it is executed to the third, thread B just comes in and executes to note 2. At this time, it is judged that the singleton is not empty and an uninitialized object is directly used. Therefore, the volatile keyword is used to prohibit instruction reordering.

Tip: what is instruction reordering?

Reordering is a means by which the compiler and processor reorder the instruction sequence to optimize the program performance.

Rules for reordering instructions:

  • Happens Before
  • As if serial: no matter how reordering (compiler and processor to improve parallelism), the execution results of (single threaded) programs cannot be changed
  • The CPU will not reorder the task operations, and the compiler and processor will only reorder the instructions without data dependency.

What is data dependency

If two operations access the same variable and one of the two operations is a write operation, there is a data dependency between the two operations.

nameCode exampleexplain
Read after writea=1;b=aAfter writing a variable, read the position again
Write after writea=1;a=2After writing a variable, write this position
Write after readinga=b;b=1After reading a variable, write this position

In the above three cases, a and b have "data dependency", and we should also pay attention to it. The data dependency mentioned here refers to the sequence of instructions executed by a single processor and the operations executed in a single thread. There is no data dependency between multiprocessors and different threads.

Keywords: Java

Added by toddg on Fri, 24 Dec 2021 00:49:28 +0200