The introduction
The last article talked about the Java memory model, where we said that the JMM is built on the happens-before principle.
Why do you say that? Because during the execution of Java programs, the compiler and processor make a series of optimizations to the code we write to improve the efficiency of the program. This includes “reordering” instructions.
Reordering causes our code to not execute in the order in which it was written, so the reason why we don’t mess up after execution is that the Java memory model follows the happens-before principle. Under the happens-before rule, execution doesn’t change no matter how the program is reordered, so we don’t see program results go wrong.
reorder
What is reordering? In plain English, the compiler and processor modify the order of instruction execution to optimize program performance.
Reordering occurs at various stages of program execution, including compiler flush, instruction level parallel flush, and memory system reordering. We won’t break down each reordering process here, just that reordering causes our code to not execute in the order we wrote it.
There is no sense of reordering in a single-threaded execution, as shown in the following code:
int a = 1; / / step 1
int b = 2; / / step 2
int c = a + b; / / step 3
Copy the code
Reordering of 1 and 2 does not affect the execution of the program. In some cases, reordering of 1 and 2 May be necessary to optimize performance. The reorder of 2 and 3 affects the execution result, so the compiler and processor do not reorder 2 and 3.
In multithreading, reordering can be sensed if the synchronization is not done properly, as in the following code:
public class AAndB {
int x = 0;
int y = 0;
int a = 0;
int b = 0;
public void awrite(a) {
a = 1;
x = b;
}
public void bwrite(a) {
b = 1; y = a; }}public class AThread extends Thread{
private AAndB aAndB;
public AThread(AAndB aAndB) {
this.aAndB = aAndB;
}
@Override
public void run(a) {
super.run();
this.aAndB.awrite(); }}public class BThread extends Thread{
private AAndB aAndB;
public BThread(AAndB aAndB) {
this.aAndB = aAndB;
}
@Override
public void run(a) {
super.run();
this.aAndB.bwrite(); }}private static void testReSort(a) throws InterruptedException {
AAndB aAndB = new AAndB();
for (int i = 0; i < 10000; i++) {
AThread aThread = new AThread(aAndB);
BThread bThread = new BThread(aAndB);
aThread.start();
bThread.start();
aThread.join();
bThread.join();
if (aAndB.x == 0 && aAndB.y == 0) {
System.out.println("resort");
}
aAndB.x = aAndB.y = aAndB.a = aAndB.b = 0;
}
System.out.println("end");
}
Copy the code
Without reordering, the program can be executed in one of four possible order:
The definition of happens-before
Happens-before defines eight rules that ensure that if A happens-before B, the results of A’s execution are visible to B and that A takes precedence over B.
- Program sequence rule: In a single thread, in the execution flow order of program code, the actions that happen (in time) before happen (in time) after.
- Manage locking rules: an UNLOCK operation happens – before after the same lock operation.
- Rule for volatile variables: Writes to a volatile variable happen — before Reads to that variable.
- Thread start rule: The start () method of the Thread object happens — before every action of this Thread.
- Thread termination rule: All operations on a Thread happen — before termination detection for this Thread can detect that the Thread has terminated by means of the end of thread.join () method, the return value of thread.isalive (), and so on.
- Thread interrupt rule: Calls to the threadinterrupt () method happen — before occurs when the interrupt is detected by the code of the interrupted thread.
- Object finalization rule: An object’s initialization happens — before the start of its Finalize () method.
- Transitivity: If A happens — before B, B happens — before C, then A happens — before C.
The happens-before rule ensures that the execution results of a single thread and properly synchronized threads will not be changed.
So why is there a program order rules guarantee, the above multithreaded execution process or appear to reorder it? This is because the happens-before rule is simply a guarantee that the Java memory model makes to the programmer. The Java memory model allows the compiler and processor to reorder the execution of a program under the happens-before rule.
And from the programmer’s point of view, it doesn’t matter whether the two operations are actually reordered, but whether the result of the program’s execution is changed.
The above program failed to synchronize multiple threads when single threads would be reordered, resulting in unexpected results.
The as – if – serial semantics
The Art of Concurrent Programming in Java explains:
As-if-serial means that the execution result of a (single-threaded) program cannot be changed, no matter how much reordering is done (to improve parallelism by the compiler and processor). The compiler, runtime, and processor must comply with the AS-IF-Serial semantics.
The as-if-serial semantics guarantee that the execution result of a single-threaded program will not be changed.
The happens-before rule is essentially the same as the happens-before rule: the happens-before rule guarantees that the execution result of a single thread and a properly synchronized multiple thread will not be changed. Are the results of the implementation of the guarantee, do not guarantee the implementation process.
This is one of the highlights of the JMM’s design: it makes it easy and correct for programmers to program, while at the same time giving the compiler and processor more freedom to optimize. Resources: In-depth Understanding of the Java Memory Model, In-depth Understanding of the Java Virtual Machine, the Art of Concurrent Programming in Java