Why do double-checked singletons add volatile

Preface
The singleton pattern is a common application pattern in development. In actual development, an instance only needs to be created once to meet the usage requirements. We choose to use the singleton pattern to avoid unnecessary memory consumption.


Let's briefly introduce what the double-checked singleton pattern is. The code is as follows:

public class Single {

    private Factory factory;

    public Factory getFactory(){
        if (factory == null){//①
            synchronized (Single.class){//②
                if (factory == null){//③
                    factory = new Factory("car","paris");
                }
            }
        }
        return factory;
    }
}

class Factory{
    String name;
    String address;

    private Factory(){

    }
    public Factory(String name,String address){
        this.name = name;
        this.address = address;
    }
}

First explain how the above code ensures that only one instance is created, for example:

  1. Suppose threads a and b acquire instances at the same time, and a and b go to ① at the same time;
  2. a first goes to ② to get the synchronization lock, and when b goes to ②, because it cannot get the lock, it starts to wait;
  3. a goes to ③ because the first loading of the factory is null, so initialize the factory and release the lock;
  4. At this time, b waits for the released lock in ②, and goes to ③. Since the factory has been initialized and not null, it returns directly to the factory; in
    the end, threads a and b get the same instance.

Next, explain the role of the volatile keyword:

  1. It ensures the visibility when different threads operate on this variable, that is, a thread modifies the value of a variable, and the new value is immediately visible to other threads;
  2. Can prohibit instruction reordering to ensure the orderliness of the code;

visibility

Visibility means that when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can immediately see the modified value.

As a simple example, look at the following code:

//线程1执行的代码
int i = 0;
i = 10;

//线程2执行的代码
j = i;

If CPU1 is executing thread 1, and CPU2 is executing thread 2. It can be seen from the above analysis that when thread 1 executes the sentence i = 10, it will first load the initial value of i into the cache of CPU1, and then assign it to 10, then the value of i in the cache of CPU1 becomes 10 , but not immediately written to main memory.

  At this point, when thread 2 executes j = i, it will first read the value of i from the main memory and load it into the cache of CPU2. Note that the value of i in the memory is still 0 at this time, so the value of j will be 0, and not 10.

  This is the visibility problem. After thread 1 modifies the variable i, thread 2 does not immediately see the value modified by thread 1.

Ordered
Order: That is, the order of execution of the program is executed according to the order of the code. As a simple example, look at the following code:

int i = 0;              
boolean flag = false;
i = 1;                //语句1  
flag = true;          //语句2

The above code defines an int type variable, defines a boolean type variable, and then assigns the two variables respectively. From the perspective of code order, statement 1 is before statement 2, so when JVM actually executes this code, will it guarantee that statement 1 will be executed before statement 2? Not necessarily, why? Instruction Reorder may occur here.

  The following explains what instruction reordering is. Generally speaking, in order to improve the efficiency of the program, the processor may optimize the input code. It does not guarantee that the execution order of each statement in the program is the same as the order in the code, but it will Ensure that the final execution result of the program is consistent with the result of the sequential execution of the code.

  For example, in the above code, whoever executes statement 1 and statement 2 first has no effect on the final program result, so it is possible that during the execution process, statement 2 is executed first and statement 1 is executed later.
  

int a = 10;    //语句1
int r = 2;    //语句2
a = a + 3;    //语句3
r = a*a;     //语句4

This code has 4 statements, then a possible execution order is:
Statement 2—> Statement 1—> Statement 3—> Statement 4

  So is it possible that this execution sequence is: Statement 2—> Statement 1—> Statement 4—> Statement 3

  Impossible, because the processor will consider data dependencies between instructions when reordering. If an instruction Instruction 2 must use the result of Instruction 1, then the processor will guarantee that Instruction 1 will be executed before Instruction 2.

  While reordering does not affect the results of program execution within a single thread, what about multithreading? Here's an example:
  

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2

//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

  In the above code, since statement 1 and statement 2 have no data dependencies, they may be reordered. If reordering occurs, statement 2 is executed first during the execution of thread 1, and thread 2 will think that the initialization work has been completed, then it will jump out of the while loop to execute the doSomethingwithconfig(context) method, and the context does not have at this time. If it is initialized, it will cause a program error.

  As can be seen from the above, instruction reordering will not affect the execution of a single thread, but it will affect the correctness of concurrent execution of threads.

  That is, for concurrent programs to execute correctly, atomicity, visibility, and ordering must be guaranteed. As long as one is not guaranteed, it is possible to cause the program to behave incorrectly.
  
The above content of visibility and orderliness is excerpted from the following blog, more detailed knowledge can be viewed on its blog, thanks for sharing http://www.cnblogs.com/dolphin0520/p/3920373.html
  
Finally began to analyze why double-checked singletons are needed Add volatile? Is there any problem without adding volatile?

First of all, what is the problem of not adding volatile, back to the earliest code we analyzed is as follows

private Factory factory;

public Factory getFactory(){
    if (factory == null){//①
        synchronized (Single.class){//②
            if (factory == null){//③
                factory = new Factory("car","paris");//④
            }
        }
    }
    return factory;
}

Or give a chestnut: threads a and b acquire factory instances at the same time, the analysis between them will not be repeated, and everyone can recall it by themselves; a arrives at the position of ④, b is waiting, and the code of ④

factory = new Factory("car","paris");

We can divide it into three parts:
a. get the object address;
b. initialize a Factory object;
c. point the factory reference to the initialized object;

According to the above orderly analysis; the code execution order of ④ will be: a—>b—>c and a—>c—>b two cases, as long as the final result is consistent, the above execution order in java memory is possible occur.
The problem lies in the execution order. There will be no problem with a->b->c sequential execution;
if a->c->b is executed, we speculate what will happen:
a arrives at the position of ④, and b is in Waiting, when a—>c—>b, the factory gets the object address, and the object is being initialized;
b acquires the lock and returns to the factory. At this time, the factory object has not been initialized or partially initialized, and the factory is used for other operations. Causes a null pointer or operation inconsistent with the expected result.
Adding the volatile keyword before the factory can ensure that the execution order is a—>b—>c because c is modified with volatile, if you don’t understand, you can go back to the above chapter for ordering .

In the end, double-checked singletons take advantage of volatile's ability to prohibit instruction reordering and ensure code ordering.

The above is all the content, there are mistakes or inappropriate places, I hope everyone can point out in time and make progress together.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325577334&siteId=291194637