One, foreword

Java happens-before is a set of rules that govern how the JVM and CPU can improve performance through instruction reordering. The concurrency performance can be improved by adjusting variable interdependence between multiple threads without affecting the final result. Without this rule, the reordering of instructions is likely to result in the wrong result.

Second, order rearrangement

Modern cpus are multi-CPU or multi-core architectures, as we mentioned in White 6. Therefore, multi-CPU architectures can execute in parallel when instructions are not dependent on each other. If instructions are interdependent, parallel execution can go wrong.

  • Instructions are not interdependent and can be executed in parallel
a = b + c
x = y + z
Copy the code
  • Instructions are interdependent and cannot be executed in parallel
a = b + c
x = a + y
Copy the code

As you can see, the second instruction depends on the calculation result of the first instruction, and if the two instructions are executed in parallel, the value of x will be incorrect.

  • Instruction part dependency
a = b + c  // 1
x = a + y  // 2
i = j + k  // 3
o = p + q  // 4
Copy the code

In the above case, we can rearrange instructions to execute concurrently to improve performance:

a = b + c  // 1
i = j + k  // 3
o = p + q  // 4
x = a + y  // 2
Copy the code

As we have seen, instruction reordering is allowed as long as it does not change the semantics of the program.

Three, multi – CPU instruction rearrangement problem

Let’s use producing a frame/drawing a frame as an example:

public class ReOrderProblem { private boolean hasNewFrame = false; private Object frame = null; private long taken = 0; private long gen = 0; public void genFrame(Object frame) { this.frame = frame; gen ++; hasNewFrame = true; } public Object takeFrame() { while (! // Wait... Until there's a new frame} Object o = frame; taken ++; hasNewFrame = false; return o; }}Copy the code

In the “genFrame” method, there is no dependency on the three instructions, because the JVM/CPU will likely use instruction reordering to increase execution speed, as follows:

    public void genFrame(Object frame) {
        gen ++;
        hasNewFrame = true;
        this.frame = frame;
    }
Copy the code

From a JVM/CPU point of view, object reference passing may be slower than the other two instructions, so performance may be improved by the above adjustment, but if you do this, it will cause a “takeFrame” problem (it won’t crash, but it may draw a frame, thus wasting a CPU).

Happens-before rule

The idea behind this rule is to say, as long as you don’t change the result of the program, it’s OK to rearrange instructions. We’ve talked about this rule a little bit before, and we’re going to look at a few of them.

4.1 program order rules (in the same thread)

In the same thread, preceding operations take precedence over subsequent operations in the order of the program. We have also said that instruction rearrangement can be carried out under the condition that the result is guaranteed to remain the same, such as in the case of “instructions are not interdependent” or “instructions are partially dependent” above. When there are dependencies, instruction reordering is not allowed.

a = b + c
x = a + y
Copy the code

In this case, the commands can only be executed sequentially without reordering.

4.2 Monitor lock rules

We’ve talked about synchronized, so monitor locks are Java’s built-in locks for thread synchronization.

synchronized(object) {
    System.out.println("x = " + x);
    x = 1;
}
Copy the code

Two threads, one of which is in a synchronized block and the other is blocked because of a lock; When a thread in the synchronized block exits and another blocked thread enters the synchronized block, the value of x is the modified value of the previous thread.

4.3. Volatile writes are visible to subsequent reads

This is what we talked about earlier. When a volatile variable is modified, the JMM invalidates a copy of that variable in other threads’ working memory, and subsequent operations need to fetch the latest value from main memory. In addition to cache invalidation, we need to look at “4.4 transitivity” (involving instruction reordering of volatile versus normal variables).

4.4. Transitivity

If operation A precedes operation B and operation B precedes operation C, then operation A precedes operation C. That’s understandable. Ok, let’s look at the instructions for reordering volatile and normal variables in “4.3” :

class Example { private int x = 10; private volatile boolean v = false; public void writer() { x = 2; v = true; } public void reader() {if (v == true) { }}}Copy the code

Analysis as shown below

We can analyze it in sequence by the figure above:

  • “X = 2” happens-before “v = true” (Rule 1);
  • “V = true” happens-before “read V” (Rule 3);
  • From the above two items and the result of “Rule 4”, we can conclude: “Read x = 2”.

This is the jSR-133 enhancement to Volatile in JDK1.5!

4.5 Thread start rule

This one is about thread starting. It means that after main thread A starts child thread B, child thread B can see what the main thread does before it starts child thread B.

4.6 Thread join rules

If thread A executes thread B.join and returns successfully, all operations of thread B take precedence over join return operations.

Five, the summary

We covered the JMM in our last article, and the JMM has two parts, one is the happens-before rule, and the other is the implementation in the JVM that we discussed in our last article. Only by mastering the JMM can we better design robust and correct programs.