This is the 13th day of my participation in Gwen Challenge

This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details

I am Chen PI, an ITer of Internet Coding. I search “Chen PI’s JavaLib” on wechat and read the latest articles as soon as possible, reply to [information], and then I can get the technical materials, electronic books, interview materials of first-line big factories and excellent resume templates carefully arranged by me.

The introduction

A happens before B. A happens before B.

Happens-before (HB) is an important rule in the JMM that the result of one operation is visible to the other, specifying the order of execution between the two operations.

So why is there a rule? One is to solve the visibility problem of multithreaded shared data. The second is to solve some instructions reordering problems, JMM constraints on compiler and processor instructions reordering principles. This is the only way to ensure that the programs we write will perform the way we expect and get the results we want.

Suppose A program has two operations A and b. Operation B requires the result of operation A. The two operations can be done in the same thread or in two different threads, and happens-before ensures that the result of operation A is visible to operation B. There is A happens-before relationship between A and B.

JMM

The JMM is the Java Memory Model, which is the concurrency model for shared memory. As we know, shared variables in Java are stored in main memory, while threads have their own working memory. If a thread wants to operate on a shared variable, it will assign the value of the shared variable to its own working memory, perform the operation, and then write the latest value of the variable back to main memory.

If thread A updates the read of shared variable X and writes it back to main memory in time, thread B then reads the value of shared variable X and operates on it, this is normal and ok. However, if thread A does not write back to the main memory in time after updating the shared variable X, then thread B reads the value of the shared variable X to operate, which will cause dirty read.

To handle concurrent dirty reads, you can use a synchronization mechanism to control the order of operations between multiple threads, for example, using the synchronzied keyword. Or use the volatitle keyword to force the most recent value to be written back to the main memory so that other threads can see it. This is the embodiment of HB in JMM.

Instruction reordering

We know that the JVM optimizes the code we write, and one of the optimizations is that the compiler and processor reorder the instructions. Although these optimizations can improve program performance, it is possible that the optimized execution results are not the expected results, so the happens-before rule is required to prohibit some compile-optimized scenarios to ensure the correctness of concurrent programming.

The JMM does not prohibit instruction reordering entirely; it allows the compiler and processor to do so for reordering that does not change the result of program execution. The JMM forbids reordering that changes the result of program execution.

In the case of the most familiar double-check slack-style singleton, the web will tell you that the following program is ok.

package com.chenpi;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2021/6/22
 * @Version1.0 * /
public class Singleton {

    private static Singleton singleton = null;

    private Singleton(a) {}

    public static Singleton getInstance(a) {
        if (null == singleton) {
            synchronized (Singleton.class) {
                if (null == singleton) {
                    singleton = newSingleton(); }}}returnsingleton; }}Copy the code

If you look at the underlying code, the following line of code is not an atomic operation. In terms of JVM instruction execution, there are generally the following steps:

  1. Allocate memory to objects
  2. Initialize an object, that is, generate an instance
  3. Assigns the allocated memory address to an object that is not null

If you follow the steps above, the concurrency situation will not be a problem. However, due to JVM compilation optimizations, it is possible to cause steps 2 and 3 to be reversed, i.e. instructions to be reordered. If step 3 is performed but step 2 is not, in the concurrent case, an error may occur if another thread decides that the variable is not null and returns an uninitialized object for use.

singleton = new Singleton();
Copy the code

The above problem is solved simply by using the volatitle keyword to modify the Singleton variable, which is one of the happens-before rules.

private static volatile Singleton singleton = null;
Copy the code

Why happens-before

Happens-before is more of a middleman. Assuring us programmers that the order of execution between some operations, such as A happens-before B, guarantees that A must happen before B. It allows us to write code in a simple and understandable way without having to learn the underlying complexity such as memory models and instruction reordering. The second is to make some constraints on the compiler and processor, they can do any code optimization, but to ensure that the optimization can not change the original program execution results, mainly refers to single-threaded programs and correctly synchronized multithreaded programs, or forbid them to optimize.

What is understood to allow compiler and processor optimizations under the happens-before rule? For example, if the program has a lock operation, but the compiler analysis found that the lock can only be accessed by a single thread, then there is no need to lock the operation, can be optimized to eliminate the lock. This can improve the execution efficiency of the program, but also will not change the original execution results.

Happens-before rules

  1. Procedural order rule: a piece of code in the same thread is executed in order, i.e., the previous action is happens-before the next action. However, it is still possible for instructions to be reordered, without reordering, the result will be the same as the result of sequential execution.
  2. Happens-before Specifies the happens-before operation for unlocking a lock. That is, subsequent locking operations can sense the change of previous unlocking, and synchronized is the implementation of pipe procedure.
  3. Volatile variable rule: Updates to the valatile modified variable happens-before any subsequent actions to that variable. Learn about memory barriers and cache consistency protocols.
  4. A happens-before B, B happens-before C, A happens-before C Those of you who have studied discrete mathematics know that partial order relations, partial order relations are transitive.
  5. Thread start rule: Before the main thread starts the child thread, the actions taken by the main thread before it starts the child thread are visible to the child thread. That is, the action in the start() happens-before child thread.
  6. Thread termination rule: If a child thread terminates during the main thread execution, the actions taken before the child thread terminates are visible in the main thread. For example, execute the join method of the child thread in the main thread and wait for the child thread to complete. When the child thread finishes executing, the main thread can see all the operations of the child thread.
  7. Thread interrupt rule: a call to the threadinterrupt method happens-before is detected by the interrupt thread code.
  8. Object finalization rule: The end of an object’s constructor execution happens-before its Finalize () method.