In-depth understanding of the Java memory model (2)-reordering

Data dependence

If two operations access the same variable, and one of these two operations is a write operation, then there is a data dependency between the two operations . Data dependence is divided into the following three types:

name Code example Description
Read after write a = 1;b = a; After writing a variable, read this position again.
Write after write a = 1;a = 2; After writing a variable, write this variable again.
Read after write a = b;b = 1; After reading a variable, write this variable.

In the above three cases, as long as the execution order of the two operations is reordered, the execution result of the program will be changed.

As mentioned earlier, the compiler and processor may reorder operations. When reordering, the compiler and the processor will observe the data dependency, and the compiler and the processor will not change the execution order of the two operations with the data dependency.

Note that the data dependency mentioned here only refers to the sequence of instructions executed in a single processor and the operations executed in a single thread. The data dependency between different processors and between different threads is not considered by the compiler and the processor.

 

as-if-serial semantics

The semantic meaning of as-if-serial means: no matter how reordering (compiler and processor in order to improve the degree of parallelism), the execution result of the (single-threaded) program cannot be changed . The compiler, runtime, and processor must all comply with as-if-serial semantics .

In order to comply with the as-if-serial semantics, the compiler and processor will not reorder operations that have data dependencies, because this reordering will change the execution result. However, if there is no data dependency between the operations, these operations may be reordered by the compiler and processor. For specific explanation, please see the following code example for calculating the area of ​​a circle:

double pi = 3.14; //A 
double r = 1.0; //B
double area = pi * r * r; //C

The data dependencies of the above three operations are shown in the following figure:

As shown in the figure above, there is a data dependency between A and C, and there is also a data dependency between B and C. Therefore, in the final executed instruction sequence, C cannot be reordered before A and B (C is ordered before A and B, and the result of the program will be changed). But there is no data dependency between A and B, and the compiler and processor can reorder the execution order between A and B. The following figure shows the two execution sequences of the program:

The as-if-serial semantics protects single-threaded programs. Compilers that comply with as-if-serial semantics, runtime and processor jointly create an illusion for programmers who write single-threaded programs: single-threaded programs are programmatically executed. To be executed sequentially. The as-if-serial semantics frees single-threaded programmers from worrying about reordering disturbing them, or worrying about memory visibility issues.

 

Procedural sequence rules

According to the happens-before program sequence rules, the sample code above for calculating the area of ​​a circle has three happens-before relations:

  1. A happens- before B
  2. B happens- before C
  3. A happens- before C

The third happens-before relationship here is derived from the transitivity of happens-before.

Here A happens- before B, but B can be executed before A in actual execution (see the execution order after reordering above). As mentioned in Chapter 1, JMM does not require A to be executed before B if A happens- before B. JMM only requires the previous operation (the result of execution) to be visible to the next operation, and the previous operation is ordered before the second operation. Here, the execution result of operation A does not need to be visible to operation B; and the execution result after reordering operation A and operation B is consistent with the result of operation A and operation B executed in the happens-before order. In this case, JMM will think that this reordering is not illegal, and JMM allows this reordering.

In computers, software technology and hardware technology have a common goal: to develop parallelism as much as possible without changing the results of program execution. Compilers and processors comply with this goal. From the definition of happens-before, we can see that JMM also complies with this goal.

 

The impact of reordering on multithreading

Now let's see if reordering will change the execution result of multithreaded programs. Please see the sample code below:

class ReorderExample { 

    int a = 0; 
    boolean flag = false; 

    public void writer() { 
        a = 1;                //1 
        flag = true;          //2 
    } 
    
    public void reader() { 
        if (flag) {           //3  
            int i = a * a;    //4
            …… 
        } 
    } 

}

The flag variable is a flag used to identify whether variable a has been written. Here assume that there are two threads A and B. A first executes the writer() method, and then the B thread executes the reader() method. When thread B is executing operation 4, can you see thread A writing to shared variable a in operation 1?

The answer is : you may not be able to see it.

Since operation 1 and operation 2 have no data dependency relationship, the compiler and processor can reorder these two operations; similarly, operation 3 and operation 4 have no data dependency relationship, and the compiler and processor can also reorder these two operations. Sort. Let us first take a look at what effect might be produced when operation 1 and operation 2 are reordered? Please see the following program execution timing diagram:

As shown in the figure above, operation 1 and operation 2 have been reordered. When the program is executed, thread A first writes the flag variable flag, and then thread B reads this variable. Since the condition is judged to be true, thread B will read variable a. At this point, the variable a has not been written by thread A at all, and the semantics of the multithreaded program here is destroyed by reordering!

※Note: This article uses a red dashed arrow to indicate a wrong reading operation, and a green dashed arrow to indicate a correct reading operation.

Let us see what effect will be produced when operations 3 and 4 are reordered (with this reordering, we can explain the control dependence by the way). The following is the execution sequence diagram of the program after operation 3 and operation 4 are reordered:

In the program, there is a control dependency between operation 3 and operation 4. When there is control dependency in the code, it will affect the parallelism of the instruction sequence execution. To this end, the compiler and processor will use speculation (Speculation) execution to overcome the impact of control dependencies on the degree of parallelism. Taking the guess execution of the processor as an example, the processor executing thread B can read and calculate a*a in advance, and then temporarily store the calculation result in a hardware cache called reorder buffer ROB. When the condition of the next operation 3 is judged to be true, the calculation result is written into the variable i.

From the figure, we can see that the guess execution essentially reorders operations 3 and 4. Reordering here destroys the semantics of multithreaded programs!

In a single-threaded program, reordering operations that have control dependencies will not change the execution results (this is why the as-if-serial semantics allows reordering operations that have control dependencies); but in multithreaded programs, Reordering operations that have control dependencies may change the execution results of the program.

 

Previous articleIn-     depth understanding of the Java memory model (1)-basics
Next article In-     depth understanding of the Java memory model (3)-sequential consistency

 

Thanks to the author for his contribution to this article

Cheng Xiaoming, Java software engineer, nationally certified system analyst and information project manager. Focus on concurrent programming and work at Fujitsu Nanda. Personal email: [email protected].
---------------------
Author: World coding
Source: CSDN
Original: https://mp.csdn.net/postedit/86183376
Copyright: This article is blogger Original article, please attach the blog post link for reprinting!
 

Guess you like

Origin blog.csdn.net/dgxin_605/article/details/86181720