preface

In his last article, Prince introduced three core issues of concern in concurrent programming: visibility, order, and atomicity.

Today we continue our exploration of concurrent programming by talking about JAVA’s memory model and happens-before rules.

JAVA memory model

The JAVA memory model does not refer to the memory distribution model mentioned in our JVM column, but rather to concurrent programming.

We already know that caching causes visibility problems and instruction reordering problems, so disabling caching and instruction reordering can avoid both problems.

However, if you disable it directly, performance will be compromised, so the right approach is to disable it on demand.

Only the programmer can figure out when to disable, so for visibility and order, just provide the programmer with an API to disable on demand.

The JAVA memory model is a complex specification that can be interpreted in different ways. Essentially, the JAVA memory model specifies how the JVM can disable caching and instruction reordering on demand.

Specifically, these methods include keywords such as volatile, synchronized, and final, as well as six happens-before rules.

Volatile is not unique to JAVA. Its original meaning was to disable CPU caching, but it was semantically enhanced after JAVA1.5 by introducing a happens-before rule.

For example:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer(a) {
    x = 42;
    v = true;
  }
  public void reader(a) {
    if (v == true) {
      // What will x be here? }}}Copy the code

If thread A executes writer and thread B executes reader, if thread B finds v=true, it will also find x=42.

Happens-before rules

So let’s take a look at today’s hero, happens-before.

Happens-before specifies that the result of a previous operation is visible to subsequent operations, and that it restricts the compiler’s optimization behavior. It allows reordering of instructions caused by compiler optimization, but requires the compiler to follow the happens-before rule after optimization.

Happens-before is a bit of an arcane part of the JAVA memory model, but as we break it down bit by bit, it’s not that hard to understand.

The sequential rules of a program

This rule means that the previous action is happens-before any subsequent action in a thread, in procedural order.

This rule is relatively easy to understand, and ensures that programs are sequenced in a single thread.

Volatile variable rule

This rule means that writes to a volatile variable are happens-before subsequent reads to that volatile variable.

Disable the CPU cache to ensure visibility of variables in multiple threads.

transitivity

This rule means that if A happens-before B, and B happens-before C, then A happens-before C.

This transitivity is also well understood. What happens if you combine transitivity with volatile variable rules?

For example, in the code above, x=42 happens-before v=true, writing v=true happens-before reading v, then x=42 happens-before reading v according to the transitivity rule.

So we analyzed earlier that if thread B reads v=true, then x=42 is also visible to thread B.

The Concurrency toolkit (java.util.Concurrent) relies on volatile semantics for visibility, and transitivity is also an enhancement to the volatile keyword, ensuring both visibility and order.

Rules for locking in pipe

This rule means that a lock is unlocked happens-before a lock is unlocked later.

This rule is also very easy to understand, no lock to unlock said.

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.

There’s nothing to explain about this rule, just the literal meaning.

Thread join() rule

This one is about thread waiting. It means that the main thread A waits for the child thread B to complete (by calling the join() method of the child thread B), and when the child thread B completes (the join() method of the main thread A returns), the main thread can see the operation of the child thread. By “seeing,” of course, we mean operating on shared variables.

conclusion

Java’s memory model is a major innovation in concurrent programming, and it has two main parts, one for application developers writing concurrent programs and the other for JVM implementers.

We can understand the former in the concurrency column.

The arcane happens-before principle. Is it that hard after reading this?

Previous articles recommended:

The JVM column

Message Middleware Column

Concurrent Programming Column