In-depth understanding of JVM (2) - memory model, visibility, instruction reordering

 In the last article, we introduced the basic running process and memory structure of the JVM, and we have a preliminary understanding of the JVM. In this article, we will explore the visibility of variables in java and the possible occurrence of different java instructions in concurrency based on the memory model of the JVM. the case of instruction reordering.

memory model

    First, let's think about how a java thread needs to communicate with another thread. Let's clarify the requirements. How does a java thread notify another thread of the update of a variable? We know that instance objects and array elements in java are placed in the java heap, and the java heap is shared by threads. (We call the java heap the main memory here), and each thread is its own private memory space (called working memory). If thread 1 wants to communicate with thread 2, it must go through a similar process:

clip_image002

1. Thread 1 updates X in its working memory to 1 and refreshes it to main memory;

2. Thread 2 reads the variable X=1 from the main memory and updates it to its own working memory, so the X read by thread 2 is the updated value of thread 1.

From the above process, it can be seen that the communication between threads needs to go through the main memory, and the interaction between the main memory and the working memory requires the Java Memory Model (JMM) to manage. The following diagram demonstrates how JMM manages main memory and working memory:

clip_image004

When thread 1 needs to flush an updated variable value to main memory, it needs to go through two steps:

1. The working memory performs the store operation;

2. The main memory performs the write operation;

After completing these two steps, the variable values ​​in the working memory can be refreshed to the main memory, that is, the variable values ​​in the working memory of thread 1 and the main memory are consistent;

When thread 2 needs to read the latest value of a variable from main memory, it also needs to go through two steps:

1. The main memory performs the read operation to read the variable value from the main memory;

2. The working memory performs the load operation to update the read variable value to the copy of the local memory;

After completing these two steps, the variable value of thread 2 and the variable value of main memory are consistent.

visibility

    There is a keyword volatile in Java, what is its use? The answer is actually in the above-mentioned java inter-thread communication mechanism. Let's imagine that due to the emergence of the middle layer of working memory, there must be a delay between thread 1 and thread 2. For example, thread 1 updates variables in working memory, but still It is not refreshed to the main memory, and the variable value obtained by thread 2 is the unupdated variable value, or thread 1 successfully updates the variable to the main memory, but thread 2 still uses the variable value in its own working memory. Something went wrong. In either case, the communication between threads may not achieve the intended purpose. For example the following example:

//Thread 1 boolean stop = false; while(!stop){ doSomething(); } //Thread 2
stop
= true;

This classic example shows that thread 2 controls the interruption of thread 1 by modifying the value of stop, but in a real environment, unexpected results may occur. After thread 2 is executed, thread 1 does not immediately interrupt or even never interrupts. The reason for this phenomenon is that the variable update of thread 2 to thread 1 cannot be obtained at the first time.

But all this will not be a problem until Volatile appears. Volatile guarantees two things:

1. The variable update in the working memory of thread 1 will be forced to be written to the main memory immediately;

2. The variables in the working memory of thread 2 will be forced to be invalid immediately, which makes thread 2 have to go to the main memory to get the latest variable value.

So it is understood that Volatile guarantees the visibility of variables, because the modification of variables by thread 1 can make thread 2 visible for the first time.

instruction reordering

Regarding the ordering of instructions, let's first look at a piece of code:

int a = 0;
boolean flag = false;

//thread 1

public void writer() {

a = 1;

flag = true;

}

//thread 2

public void reader() {

if (flag) {

int i= a+1;

...... }

}

Thread 1 executes a=1, flag=true in sequence; after thread 2 judges that flag==true, set i=a+1. According to the code semantics, we may infer that the value of i is equal to 2 at this time, because thread 2 is judging When flag==true, thread 1 has executed a=1; so the value of i is equal to a+1=1+1=2; but the real situation is not necessarily the case. The reason for this problem is that two internal threads of thread 1 Statement a=1; flag=true; may be reordered and executed, as shown in the figure:

clip_image006

This is a simple demonstration of instruction reordering. Two assignment statements, although their code order is one after the other, are not necessarily executed in code order when actually executed. You may say, with this instruction reordering, isn't it a mess? The programs I write don't follow my code flow, how does this work? You can rest assured that your program will not be messed up, because there is a set of strict instruction reordering rules between java, CPU, and memory. There are rules about which ones can be rearranged and which ones cannot. The following flow demonstrates the reordering of a java program from compilation to execution:

clip_image008

The first step in this process belongs to the compiler reordering. The compiler reordering will be carried out strictly according to the JMM specification. In other words, the compiler reordering will generally not affect the correct logic of the program. The second and third steps belong to the processor reordering, and the processor reordering JMM is not easy to manage. What should I do? It will require the java compiler to add a memory barrier when generating instructions, what is the memory barrier? You can think of it as an airtight protective cover that protects java instructions that cannot be reordered, so that the processor will not reorder it when it encounters an instruction protected by a memory barrier. Regarding where to add memory barriers, what types of memory barriers are, and what role each has, these knowledge points will not be elaborated here. You can refer to the JVM specification related materials.

The following describes the logic that will not be reordered in the same thread:

clip_image010

In these three cases, if the order of a code is changed arbitrarily, the result will be very different. For such logic code, it will not be reordered. Note that this means that it will not be reordered in a single thread. If it is in a multi-threaded environment, there will still be logical problems, such as the example we gave at the beginning.

Epilogue

This article briefly introduces the simple principle of java in implementing inter-thread communication, and introduces the role of the volatile keyword. Finally, it introduces the situation that instruction reordering may occur in java. The next article will introduce the impact of parameter settings in the JVM on java programs.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325262768&siteId=291194637