It mainly explains the basic knowledge of concurrent programming in Java, including atomicity, visibility, order, and memory model JMM.

Java Series Description

From this article, I will officially start to learn Java, is said to be from now on, because the first two months have been entangled with whether to turn technology stack (attentive students can be found, I write articles before, and actually Java no relation), is now want to know, now that turn to Java technology stack, Then learn from the very beginning.

At present, I can say that I am a Java novice. I just transferred to the team, and I have been in touch with Java for two months without writing a few lines of code. Since I find that I am not good at Java, I need to make a study plan to make up for this knowledge. At present, I have set a one-year learning plan for myself, hoping to directly upgrade my Java skills from the initial level to the advanced level through one-year learning. Some students may ask “I have been learning Java for several years, and it is still at the intermediate level. I just want to say, I want to try, after all, work for such a long time, but also master a certain learning method, I believe that follow your own learning rhythm, should not be too far away from the goal, today set a Flag, I hope a year later will not slap face [face] ~~

Java series includes concurrent programming, Spring, SpringBoost, SpringCloud, Tomcat, MyBatis, Dubbo, and virtual Machines, and then some classic books reading notes, when these have a certain depth, I think my Java skills are about right.

Finally want to say is, Java many series, is a large part of the content arrangement, should be in the form of writing again, because read content, if you don’t finish it again, or do not let the program run, very easy to forget, so write is not the purpose, main is to reorganize and review the process of learning content, on the one hand, impressive, On the other hand, it is also easy to follow up.

Today is a bit too much nonsense, let’s start with concurrent programming!

Basic concepts of concurrent programming

atomic

An operation or operations, either all performed without interruption by any factor, or none performed at all.

Atomicity rejects multithreading operations. No matter multi-core or single-core, atomic quantities can only be operated by one thread at a time. In short, any operation that is not interrupted by the thread scheduler during its entire operation is considered atomic. For example, a=1 is atomic, but a++ and a+ =1 are not atomic. Atomic operations in Java include:

  • Basic types of read and assign operations, and assignments must be values to variables, variables to each other is not atomic operations;
  • All reference assignment operations;
  • All operations on all classes in the java.concurrent.atomic.* package.

visibility

When multiple threads access a variable and one thread changes the value of the variable, the other threads can immediately see the changed value.

In a multithreaded environment, one thread’s operation on a shared variable is invisible to other threads. Java provides volatile to ensure visibility. When a variable is volatile, it indicates that thread local memory is invalid. When a thread changes a shared variable, it is immediately updated to main memory, and other threads read the shared variable directly from main memory. Of course, both Synchronize and Lock guarantee visibility. Synchronized and Lock ensure that only one thread at a time acquires the Lock and executes the synchronization code, flushing changes to variables into main memory before releasing the Lock. So visibility is guaranteed.

order

That is, the order in which the program is executed is the order in which the code is executed.

Orderliness in the Java memory model can be summarized as: if viewed within the thread, all operations are ordered; If you observe another thread in one thread, all operations are unordered. The first part of the sentence refers to the phenomenon of “instruction reordering” and “main-main-memory synchronization delay”.

In the Java memory model, the compiler and processor are allowed to reorder instructions for efficiency. Reordering does not affect single-thread execution, but it does affect multithreading. Java provides volatile to ensure some order. The most famous example is the DCL (double-checked lock) in the singleton pattern. In addition, order can be guaranteed by synchronized and Lock. Synchronized and Lock ensure that one thread executes the synchronized code at each moment, which is equivalent to ordering the threads to execute the synchronized code sequentially, which naturally ensures order.

In order to understand visibility and order, it is important to understand “memory model”, “reorder”, and “memory barrier” because these three concepts are closely related to them.

The memory model

The JMM determines when a thread’s write to a shared variable is visible to another thread. The JMM defines an abstract relationship between threads and main memory: Shared variables are stored in Main Memory. Each thread has a private Local Memory. Local Memory holds a copy of the Main Memory used by the thread.


For common shared variables, thread A’s modification to A value occurs in thread A’s local memory and has not yet been synchronized to main memory. Thread B already caches the old value of this variable, so the shared variable values are inconsistent. To solve the problem of invisibility of shared variables in the multithreaded model, volatile, synchronized, final, etc., can be used. In this case, the communication process of A and B is as follows:

  • First, thread A flusher the updated shared variable from local memory A to main memory.
  • Thread B then goes into main memory to read the shared variables that thread A has updated previously.

The JMM provides visibility into memory for Java programmers by controlling the interaction between main memory and the local memory of each thread. It is important to note that the JMM is an abstract memory model, so local memory, main memory, is an abstract concept that does not necessarily correspond to the actual CPU cache and physical memory.

To sum up, the memory model JMM controls visibility of shared variables by multiple threads!!

reorder

Reordering is a method by which compilers and processors sort sequences of instructions to optimize program performance.

Reordering needs to follow certain rules:

  • A reorder operation does not reorder operations that have data dependencies. For example: a = 1; b=a; This sequence of instructions, since the second operation depends on the first operation, will not be reordered at compile time and processor runtime.
  • The purpose of reordering is to optimize performance, but no matter how reordering is done, the results of a single-threaded program cannot be changed. For example: a = 1; b=2; The first (a=1) and second (b=2) operations may be reordered because there is no data dependence, but the operation c=a+b will not be reordered because the final result is guaranteed to be c=a+b=3.

Reordering in a single-threaded environment is guaranteed to be correct, but in a multi-threaded environment, reordering can occur and affect the results, as shown in the following example code:

class ReorderExample {

  int a = 0;

  boolean flag = false;

  public void writer(a) {

      a = 1;                   / / 1

      flag = true;             / / 2

  }

  Public void reader(a) {

      if (flag) {                / / 3

          int i =  a * a;        / / 4

          System.out.println(i);

      }

  }

}

Copy the code

The flag variable is a flag that indicates whether variable A has been written. Here we assume that we have two threads A and B, with A first executing writer() and then THREAD B executing reader(). What is the output of thread B when it performs operation 4?

The answer is: it could be 0, it could be 1.

Since operations 1 and 2 have no data dependencies, the compiler and processor can reorder these two operations; Similarly, operations 3 and 4 have no data dependencies, and the compiler and processor can also reorder these two operations. Let’s first look at what might happen when operations 1 and 2 reorder. Take a look at the sequence diagram below:


As shown in the figure above, operations 1 and 2 are reordered. When the program executes, thread A first writes the flag variable, which thread B then reads. Since the condition is judged true, thread B will read variable A. At this point, variable A has not been written by thread A at all, where the semantics of the multithreaded program are broken by reordering! The final output of I is 0.

For example, thread A executes writer() first, and thread B executes reader(). In fact, this reorder is for thread B, not thread A ha!

Writer () is executed by thread B in the order of A =1 and flag=true. Writer () is executed by thread B in the order of A =1 and flag=true. Is it much clearer?

Now let’s see what happens when operations 3 and 4 are reordered (with this reordering, we can incidentally illustrate control dependencies). The following is a sequence diagram of the program after reordering operations 3 and 4:


In the program, operations 3 and 4 have control dependencies. When there is a control dependency in the code, it will affect the parallelism of instruction sequence execution. To do this, compilers and processors employ a guess execution to overcome the effect of controlling correlation on parallelism. In the case of processor guess execution, the processor of thread B can read and calculate a* A ahead of time, at which point the result is 0, and then temporarily store the result in a hardware cache named reorder Buffer ROB. When the condition for the following operation 3 is judged to be true, the result is written to variable I.

As we can see from the figure, the guess execution essentially reorders operations 3 and 4. Reordering breaks the semantics of multithreaded programs here! Since the value of temp is 0, the final output of I is 0.

How to avoid the effect of reordering on multithreading? The answer is “memory barrier”!

The memory barrier

To ensure memory visibility, variables can be modified by volatile, final, and so on. The Java compiler inserts memory barrier instructions at the appropriate locations in the generated instruction sequence to prohibit a particular type of handler from reordering. The memory barrier has three main functions:

  • It ensures that instruction reordering does not place subsequent instructions in front of the memory barrier, nor does it place previous instructions behind the barrier; That is, by the time the memory barrier instruction is executed, all operations in front of it have been completed;
  • It forces changes to the cache to be written to main memory immediately.
  • If it is a write operation, it invalidates the corresponding cache line in the other CPU.

Suppose I volatile the falg variable in the example above:

class ReorderExample {

  int a = 0;

  boolean volatile flag = false;

  public void writer(a) {

      a = 1;                   / / 1

      flag = true;             / / 2

  }

  Public void reader(a) {

      if (flag) {                / / 3

          int i =  a * a;        / / 4

          System.out.println(i);

      }

  }

}

Copy the code

There are also some rules for volatile to forbid instruction reordering, which will be explained in the next chapter for reasons of space. According to the happens-before rule, the happens-before relationship can be divided into two categories:

  1. 1 happens before 2; 3. What happens before 4.
  2. 2 happens before 3 according to the volatile rule.
  3. According to the transitive rule, 1 happens before 4.

A happens-before rule is a code dependency established by a reordering rule.

Tips: I understand the rule of 1 and 3, but for 2, why “2 happens before 3”? Do you remember the “memory model” mentioned earlier? If flag=true is volatile, thread A will flush it directly into memory, and thread B will see it immediately. Therefore, 2 must be in front of 3, and 3 cannot be reordered before 2. (then clue, here there is A premise condition, is thread A performed, can the inside of the executing thread B logic, because the thread is not performed, A flag is always false, thread B simply into the main process, so you also can understand directly to thread A after the execution, execution thread B again, there is such A successively relationship.)

The graphical representation of the happens-before relationship above is as follows:


In the figure above, each arrow links two nodes, representing a happens-before relationship. Black arrows indicate program order rules; The orange arrow indicates the volatile rule; The blue arrows represent the happens-before guarantees provided by combining these rules.

Here thread A writes A volatile variable, and thread B reads the same volatile variable. All shared variables visible to thread A before writing A volatile will become visible to thread B immediately after thread B reads the same volatile variable.

conclusion

In this article, we introduce the following concepts: “Memory model JMM”, “reordering” and “memory barrier”, which are important for understanding volatile, synchronized, and final Java concurrent programming. And avoiding all kinds of pits is really really important!! So this piece of knowledge must be! Must have!!!!! Want to!!! To master.

Not counting what I read before, I spent an afternoon just writing this article. This article involves knowledge, consulted a large amount of online information, I can say that I wrote this article is better than most of the articles online, I saw xiao-ming cheng “in-depth understanding of the Java memory model”, although the content inside is very good, but a lot of knowledge in some repetitive, I only took one of the most important part, and then also have online articles, write very classic, But for some concepts and examples, the depth is not enough, I combined their advantages and disadvantages, and then sorted out this article, after reading this article in detail, you should feel better to understand a lot of other articles.

Afterword.

This article is my introduction to Java concurrent programming, and I will continue to write volatile, synchronized and final respectively. The relevant content has been read, and the subsequent output can be directly sorted out. In fact, I do not know these basic knowledge to learn which degree just calculate OK, then write while learning, such as basic knowledge to write about the same, began to write actual combat part.

Welcome everyone to like a lot, more articles, please pay attention to the wechat public number “Lou Zai advanced road”, point attention, do not get lost ~~