🎓 Do your best and obey the destiny. I am a postgraduate student in Southeast University and a summer intern in Java background development in Ctrip. I love fitness and basketball, and I am willing to share what I have seen and gained related to technology. I follow the public account @flying Veal and get the update of the article as soon as possible

🎁 This article has been included in the “CS-Wiki” Gitee official recommended project, has accumulated 1.7K + STAR, is committed to creating a perfect back-end knowledge system, in the road of technology to avoid detours, welcome friends to come to exchange and study

🍉 If you do not have a good project, you can refer to a project I wrote “Open source community system Echo” Gitee official recommended project, so far has accumulated 700+ STAR, SpringBoot + MyBatis + Redis + Kafka + Elasticsearch + Spring Security +… And provide detailed development documents and supporting tutorials. Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo


Happens-before, the Art of Concurrent Programming in Java:

Happens-before is a central JMM concept. Understanding happens-before is key to understanding the JMM for Java programmers.

Understanding the Java Virtual Machine – 3rd Edition

Happens-before is the soul of the JMM and is a very useful means of determining whether data is competing and threads are safe.

These two sentences alone, I think, are enough to show the importance of the happens-before principle.

So why is happens-before called the heart and soul of the JMM?

Born this way.

JMM designer’s problem and perfect solution

We learned about the JMM and its three properties. In fact, from the perspective of a JMM designer, visibility and orderliness are two contradictory aspects:

  • On the one hand, we want the memory model to be easy for programmers to understand and program, and for that reason the DESIGNERS of the JMM provide programmers with a strong enough guarantee of memory visibility, known in the jargon as a “strong memory model.”
  • Compilers and processors, on the other hand, want the memory model to tie them down as little as possible so that they can make as many optimizations (such as reordering) to improve performance, so the JMM designers have to be as loose as possible on the compiler and processor, known in the jargon as the “weak memory model.”

The happens-before principle provides a perfect solution to this problem, starting with JDK 5, in the JSR-133 memory model. The Java Memory Model and Thread Specification defines the happens-before relationship as follows:

1) If an action happens-before another, the execution result of the first action will be visible to the second action, and the execution order of the first action precedes the second action.

2) The existence of a happens-before relationship between two operations does not mean that specific implementations of the Java platform must be executed in the order specified by the happens-before relationship. Reordering is not illegal if the result of the reordering is the same as the result of the happens-before relationship (that is, the JMM allows reordering)

Not hard to understand, the first definition is the JMM’s promise of a strong memory model for programmers. From A programmer’s perspective, the happens-before relationship can be understood this way: if A happens-before B, the JMM will assure the programmer that the result of A’s action will be visible to B, and that A takes precedence over B in execution order. Note that this is just a guarantee the Java memory model makes to the programmer!

Note that unlike the as-if-serial semantics, which can only be applied to A single thread, the two operations A and B mentioned here can be either within A thread or between different threads. That is, happens-before provides a guarantee of memory visibility across threads.

For this first definition, let me give you an example:

// The following operations are performed in thread A
i = 1; // a

// The following operations are performed in thread B
j = i; // b

// The following operations are performed in thread C
i = 2; // c
Copy the code

Assuming that operation A on thread A is happens-before operation B on thread B, then we can be certain that after operation B is executed, variable j must be equal to 1.

There are two bases for drawing this conclusion: first, according to the happens-before principle, the result of a’s operation is visible to B, that is, the result of “I =1” can be observed; Second, thread C is not running yet, and no other thread will modify variable I after thread A finishes.

Now consider thread C, we still keep a happens-before B, and C appears between the operations of A and B, but there is no happens-before relationship between C and B, which means THAT B does not necessarily see the results of C’s operations. Then the result of b, the value of j, is uncertain, could be 1 or 2, and this code is not thread safe.


Again, the second definition of happens-before is the JMM’s guarantee of the compiler and processor’s weak memory model, which places constraints on compiler and processor reordering while giving sufficient room for manipulation. In other words, the JMM is following a basic principle: the compiler and processor can be optimized as long as the execution of the program (i.e., single-threaded programs and properly synchronized multithreaded programs) does not change.

The JMM does this because the programmer doesn’t care if the two operations are actually reordered, the programmer only cares that the result of the execution can’t be changed.

The text may not be easy to understand, but let’s take an example to explain the second definition: Just because there is a happens-before relationship between two operations does not mean that the Java platform implementation has to execute in the order specified by the happens-before relationship.

int a = 1; 		// A
int b = 2;		// B
int c = a + b;	// C
Copy the code

According to the happens-before rule (described below), the code above has three happens-before relationships:

1) A Happens B

2) A Happens B Happens C

3) A Happens C

It can be seen that in the 3 happens-before relationships, the 2nd and 3rd are required, but the 1st is not.

That is, although A happens-before B, reordering between A and B does not change the execution of the program at all, so the JMM allows the compiler and processor to perform this reordering.

The following JMM design is more intuitive:

In order to prevent Java programmers from having to learn complex reordering rules and how to implement them in order to understand the memory visibility guarantees provided by the JMM, the JMM has a straightforward happens-before principle. A happens-before rule corresponds to a reordering rule for one or more compilers and processors, so we just need to understand happens-before.

8 happens-before rules

Jsr-133 :Java Memory Model and Thread Specification defines the following happens-before rules, which are the “natural” happens-before relationships in the JMM, These happens-before relationships exist without the assistance of any synchronizer and can be used directly in coding. If the relationship between two operations is not in this column and cannot be deduced from the following rules, they are not guaranteed to be ordered, and the JVM can reorder them at will:

1) Program Order Rule: In a thread, the happens-before actions written earlier take place before those written later, in the Order of control flow. Note that we are talking about the control flow sequence, not the program code sequence, because we have branches, loops, and so on to consider.

This is easy to understand, and it fits our logic. Take our example above:

int a = 1; 		// A
int b = 2;		// B
int c = a + b;	// C
Copy the code

The above code has three happens-before relationships according to the program order rule:

  • A Happens-before B
  • B Happens-before C
  • A Happens-before C

2) Monitor Lock Rule: an UNLOCK operation occurs first when another Lock operation is performed on the same Lock. What must be emphasized here is “the same lock”, and “behind” refers to the sequence of time.

This rule is actually specific to synchronized. The JVM does not expose lock and unlock operations directly to the user, but it does provide higher-level bytecode instructions Monitorenter and Monitorexit to implicitly use them. These two bytecode instructions are reflected in Java code as synchronized blocks.

Here’s an example:

synchronized (this) { // Automatic lock here
	if (x < 1) {
        x = 1; }}// It will be unlocked automatically
Copy the code

According to the pipe lock rule, assuming the initial value of x is 10, the value of x will change to 1 after thread A finishes executing the code block, and when thread B enters the code block, it can see the write operation of thread A on X, that is, thread B can see x == 1.

3) Volatile Variable Rule: Writes to a volatile Variable occur first and then reads occur later, again in chronological order.

This rule is a significant addition to the semantics of volatile in JDK 1.5, and it makes visibility a snap.

Here’s an example:

Suppose that after thread A executes writer(), thread B executes reader().

Happens-before 2; 3 happens-before 4.

Follow the rule for volatile variables: 2 happens-before 3.

According to the transitive rule: 1 happens-before 3; 1 happens-before 4.

That is, if thread B reads “flag==true” or “int I = a”, thread A’s “A =42” setting is visible to thread B.

See below:

4) Thread Start Rule: The Start () method of the Thread object occurs first for each action of the Thread.

For example, after main thread A starts child thread B, child thread B can see everything that the main thread did before it started child thread B.

5) Thread Termination Rule: All operations in a Thread occur before the Thread terminates. We can detect whether the Thread has terminated by checking whether the join() method of the Thread object has ended or the return value of isAlive() of the Thread object.

6) Thread Interruption Rule: A call to the interrupt() method occurs when code in the interrupted Thread detects that an interrupt has occurred. This can be detected through the interrupted() method of the Thread object.

7) Finalizer Rule: The finalization of an object (the end of constructor execution) takes place at the beginning of its Finalize () method first.

8) Transitivity: If operation A precedes operation B and operation B precedes operation C, it follows that operation A precedes operation C.

“Temporal antecedent” and “antecedent”

The eight rules mentioned above constantly mention the order of time, so what is the difference between happens-before and happens-before?

Does the fact that an operation is “pre-occurring in time” mean that the operation will be “pre-occurring”? Does it follow that an operation is antecedent that the operation must be antecedent in time?

Unfortunately, neither of these inferences is true.

Here are two examples:

private int value = 0;

// thread A calls
pubilc void setValue(int value){    
    this.value = value;
}

// thread B calls
public int getValue(a){
    return value;
}
Copy the code

If there are threads A and B, thread A calls setValue(1) first and thread B calls getValue() on the same object, what is the return value that thread B receives?

Let’s take a look at the eight rules of happens-before above:

Since the two methods are called by threads A and B and are not in the same thread, the program order rule does not apply here;

Since there is no synchronized block, lock and unlock cannot occur naturally, so the pipe locking rule does not apply here.

Similarly, rules for volatile variables, thread start, terminate, interrupt, and object termination are completely irrelevant here.

Since there is no applicable happens-before rule, rule no. 8 is transitive.

Therefore, we can determine that although thread A is ahead of thread B in terms of operation time, we cannot say that A happens-before B, i.e., the result of thread A’s operation may not be visible to B. So, this code is thread unsafe.

How easy is it to fix this? Since it doesn’t satisfy the happens-before principle, I’ll just modify it so that it does. For example, you can apply the pipe lock rule by modifying Getter/Setter methods with synchronized; For example, make value volatile so that rules for volatile variables can be applied.

This example demonstrates that just because an operation is “prior in time” does not mean that it will be “happens-before.”

Here’s another example:

// The following operations are performed in the same thread
int i = 1;
int j = 2;
Copy the code

Suppose that the two assignments in this code are in the same thread. Then, according to the program order rule, “int I = 1” Happens before “int j = 2”, but remember the second definition of happens-before? Remember from the previous article that the JMM actually follows the principle that the compiler and processor can be optimized as long as they do not change the execution results of the program (i.e., single-threaded programs and properly synchronized multithreaded programs).

So, “int j=2” is likely to be executed first by the processor because it doesn’t affect the final result of the program.

This example, then, demonstrates that an operation “happens-before” does not necessarily mean that the operation is “temporally prior”.

Therefore, to sum up the above two cases, we can come to a conclusion that there is basically no causal relationship between the happens-before principle and the time order, so we should try not to be disturbed by the time order when we measure the concurrency safety problem, and everything must follow the happens-before principle.

Happens-before and as – if – serial

The JMM is following the basic principle that the compiler and processor can be optimized as long as the execution results of the program (i.e., single-threaded programs and properly synchronized multithreaded programs) are not changed.

Recall the as-if-serial semantics: no matter how reordered, the execution result of a program in a single-threaded environment cannot be changed.

Did you notice that? The happens-before relationship is essentially the same thing as the as-if-serial semantics, both intended to maximize the parallelism of program execution without changing the results of program execution. Except that the latter can only work with a single thread, while the former can work with properly synchronized multithreading:

  • The as-if-serial semantics guarantee that the execution result of a program in a single thread is not changed, and the happens-before relationship guarantee that the execution result of a properly synchronized multithreaded program is not changed.
  • The as-if-serial semantics create a fantasy for programmers who write single-threaded programs: single-threaded programs are executed in sequence. The happens-before relationship creates a fantasy for programmers who write properly synchronized multithreaded programs: properly synchronized multithreaded programs are executed in the order specified by happens-before.

References

  • The Art of Concurrent Programming in Java
  • “Understanding the Java Virtual Machine in Depth – Version 3”

| flying veal 🎉 pay close attention to the public, get updates immediately

  • I am a postgraduate student in Southeast University and a summer intern in Java background development of Ctrip. I run a public account “Flying Veal” in my spare time, which was opened on 2020/12/29. Focus on sharing computer fundamentals (data structure + algorithm + computer network + database + operating system + Linux), Java technology stack and other related original technology good articles. The purpose of this public account is to let you can quickly grasp the key knowledge, targeted. Pay attention to the public number for the first time to get the article update, we progress together on the way to growth
  • And recommend personal maintenance of the open source tutorial project: CS-Wiki (Gitee recommended project, has accumulated 1.6K + STAR), is committed to creating a perfect back-end knowledge system, in the road of technology to avoid detdettions, welcome friends to come to exchange learning ~ 😊
  • If you don’t have any outstanding projects, you can refer to the Gitee official recommended project of “Open Source Community System Echo” written by me, which has accumulated 700+ star so far. SpringBoot + MyBatis + Redis + Kafka + Elasticsearch + Spring Security +… And provide detailed development documents and supporting tutorials. Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo Echo