Reordering is a method by which value compilers and processors reorder instruction sequences to optimize program performance. But it can’t be reordered at will, not in any way you want, it has to satisfy the following two conditions:
- In a single thread environment cannot change the results of the program running;
- Data dependencies cannot be reordered
The two points boil down to one thing: the JMM allows arbitrary ordering, which cannot be deduced by the happens-before principle.
Data dependency
If two operations access the same variable, and one of them is a write operation, there is a data dependency between the two operations. There are three types of data dependencies
The name of the | code | instructions |
---|---|---|
Writing after reading | a=1; b=a; | Write a variable first, then read the variable |
Write after | a=1; a=2 | Let’s write this variable first and then let’s write this variable |
Got to write | a=b; b=1; | Read the variable first, and then write the variable |
In all three cases, reordering the order of the two operations changes the result of the program’s execution. The compiler and processor follow the data dependencies when reordering, and the compiler and processor do not change the order in which two operations are executed in a data dependency relationship.
The as – if – serial semantics
The as-if-serial semantics mean that all operations can be reordered for optimization, but you must ensure that the result of the reordered execution cannot be changed. The compiler, runtime, and processor must obey the as-if-serial semantics. Note that as-IF-serial is only guaranteed for single-threaded environments, not multithreaded environments. For example:
Double PI = 3.14; //A double r = 1.0; //B double area = pi*r*r; //CCopy the code
Operations A, B, and C have the following relationships: OPERATIONS A and B do not have A data dependence relationship, and operations A and C and B and C have A data dependence relationship. Therefore, when reordering, operations A and B can be sorted randomly before operations C. The sequence can be A -> B -> C or B -> A -> C. But no matter what the order of execution, the final result of C is always equal to 3. The as-if-serail semantics protect single-threaded programs by ensuring that the end result of the program is always the same under reordering conditions.
Procedural order rule
In fact, in the previous code, they had this happen-before relationship:
- A happens-before B
- B happens-before C
- A happens-before C
1, 2 are procedural order rules, and 3 are transitivity. But why does A happens-beforeB exist when overordering is possible and B is executed before A? A is visible to B, but the execution results of A do not need to be visible to B, as opposed to the program, and their reordering does not affect the results, so the JMM allows this reordering. It is important to understand that both software and hardware technologies have a common goal: to achieve as much parallelism as possible without changing the results of program execution.
The impact of reordering on multithreading
In single-threaded environments reordering cannot affect the final result due to as-if-serial semantics, but what about multithreaded environments? For example
int a = 0;
boolean flag = false; /** * thread A executes */ public voidwriter(){
a = 1; // 1
flag = true; // 2} /** * thread B to execute */ public voidread() {if(flag){ // 3 int i = a + a; / / 4... }}Copy the code
Thread A executes writer() and thread B executes read(). Can thread B read A = 1 when it executes?
The answer is: not necessarily
Since there is no data dependency between operation 1 and operation 2, reorder can be done, and there is no data dependency between operation 3 and operation 4, they can also be reordered, but there is a control dependency between operation 3 and operation 4.
Suppose reordering between operation 1 and operation 2:
Thread B cannot read thread A’s A value in this order, where the semantics of multithreading have been broken by reordering.
This is because thread A first writes the flag variable during program execution, and then thread B reads it. Since the condition is judged to be true, thread B will read variable A before variable A has been written by thread A.
Suppose reordering between operations 3 and 4:
In the program, operations 3 and 4 have control dependencies. When the code has control dependence, it will affect the parallelism of instruction sequence execution. To do this, the compiler and processor use guess execution to overcome the effect of control dependency on parallelism, where “temp=a*a;” This is the guess execution that the compiler and processor take, then temporarily store the result in a hardware cache called the resort cache, and write the result to variable I when the condition for operation 3 is judged to be true.
The guess execution essentially reorders operations 3 and 4. Reordering breaks the semantics of multithreading here.
The impact of reordering
Reordering does not affect the execution results of a single-threaded environment, but it does break the execution semantics of multithreading.
The Art of Concurrent Programming in Java
My humble opinion, thank you for reading. Welcome to the discussion,Personal blog
JAVA concurrency (1) Concurrency programming challenges
JAVA concurrency (2) Underlying implementation principles of synchronized and volatile
JAVA concurrency (3) lock status
JAVA concurrency (4) atomic operation implementation principle
JAVA concurrency (5) happens-before
JAVA concurrency (6) reordering
JAVA concurrency (7) Analyze volatile against the JMM
JAVA concurrency (8) Final domain
JAVA concurrency (9) In-depth analysis of DCL
JAVA Concurrency (10) Concurrency programming basics