How to solve concurrency visibility and order

preface

The three problems of concurrency are visibility, atomicity and orderliness. How to solve the visibility and orderliness problems?

Visibility is caused by CPU cache, and orderliness is caused by compiler optimization. The crude way to completely solve the two problems is to disable CPU cache and prohibit compiler optimization, so this will directly lead to the program running speed straight down, affecting the program running is not desirable.

CPU caching and compiler optimizations can only be disabled on demand. When should CPU caching and compiler optimizations be disabled? This is known only to programmers, so the JVM came up with the JMM (JAVA Memory Model), which regulates happens-before rules and methods (final, synchronized, volatile) for disabling CPU caching on demand and compiler optimizations.

methods

final

Once a final variable is assigned ina constructor and the constructor does not escape, the value of the variable is visible to other threads.

What is escape?

Escape refers to the destruction of encapsulation. An operation on an object, for example, assigns the object’s this value to an external global variable so that the global variable can bypass the object’s encapsulation interface and access the members of the object directly. This is called escape.

public class FinalTest{ final int x; Public FinalTest() {x = 12; Global.obj = this; global.obj = this; global.obj = this; global.obj = this; global.obj = this; }}Copy the code

synchronized

Concurrency primitives in pipe procedures that can modify code blocks and methods to ensure that only one thread accesses a resource at a time.

volatile

Volatile is not unique to Java and exists in other languages such as C, where the original semantics are to disable caching.

Volatile int x = 0; The compiler is told to read and write this variable directly from memory without using the CPU cache, and the property is also directly refreshed into memory after modification.

But there is code like this: Thread A calls write, what is the value of x accessed by thread B, and is the value of x changed by thread A accessible by thread B?

This is explained by referring to the happens-before rule.

class volatileTest{ int x = 0; volatile boolean v= false; public void write(){ x = 12; v = true; } public void read(){ System.out.println(x); System.out.println(v); }}Copy the code

The rules

Happens-before rules

What is the happens-before rule

The literal translation of the name means that it happens first, but what it really means is that the result of one previous operation is visible to another.

The six rules

The sequential rules of a program

Happens-before refers to the happens-before action in a thread, in order of procedure.

class volatileTest{ int x = 0; volatile boolean v= false; public void write(){ x = 12; v = true; }}Copy the code

x=12; Happense – Before in v = true; X =12 is swapped with v=true, but v is not volatile because volatile writes insert memory barriers before and after (StoreStore barriers | volatile writes | StoreLoad barriers).

StoreStore barrier: Prevents normal writes above and volatile writes below from reordering.

The StoreLoad barrier: Prevents volatile writes above from reordering potentially volatile reads/writes below.

Volatile variable rule

Happens-before refers to a write to a volatile variable Before a read to a volatile variable.

This place is forced to refresh the CPU cache, it will inform other CPU reads the data is invalid, you need to read, then lock is a message bus, when the message bus is locked, other CPU is unable to read data in memory, wait for after the completion of the CPU, the calculation result will refresh to memory, the final release of the message bus.

Transitivity rule

A happens-before B B happens-before C

This explains why the non-volatile X variable in this example can be accessed by another thread after being modified by one thread.

class volatileTest{ int x = 0; volatile boolean v= false; public void write(){ x = 12; v = true; } public void read(){ System.out.println(v); System.out.println(x); }}Copy the code

Here’s why:

  1. X =12 Before v=true, x=12, v=true
  2. Volatile write operations are visible to volatile read operations according to the volatie rule. So v=true is visible to reading v.
  3. Read operations on volatile variables are visible according to the transitivity rule x=12.

Here are the volatile keyword enhancements in JDK1.5, as well as the visibility that the JUC (java.util.Concurrent) concurrency toolkit relies on for volatile.

Rules for locking in pipe

Refers to a Lock unlock Happense – Before the Lock Lock operations, and pay special attention to here is monitor is a kind of general synchronization primitives, program structure for multithreaded mutually exclusive access to a Shared variable, the Java middle finger is synchronized synchronization primitives, does not include the Lock.

public class Test{ int x = 10; Public void test(){synchronized (this) {synchronized (this) {public void test(){synchronized (this) {synchronized (this); } // Automatic unlock here}}Copy the code

Thread A accesses the initial value x=10, and acquires lock this and changes x to 12. The lock is unlocked successfully, referring to the lock rules in the pipe procedure.

Thread start rule

This parameter refers to the happens-before operations performed by child thread B after the main thread A starts child thread B.

Thread B = new Thread(()->{// in this case, access the shared variable a==2}); // share variable a change value a = 2; // start the child thread.Copy the code

As long as the main thread changes the shared variable before B.start(), B’s run method can access the latest value.

Thread Join rule

It refers to the happens-before thread B changes the shared variable after thread A waits for thread B to complete execution.

Thread B = new Thread(()->{ }); B.start(); B.join(); // The main thread can access the shared variable B ==3 modified by thread BCopy the code