Java concurrent programming principle and Combat 41: reordering and Happens-before

Source: Internet
Author: User

I. Conceptual understanding

First, let's look at what reordering is: reordering refers to a means by which the compiler and the processor reorder the sequence of instructions in order to optimize the performance of the program.

From the Java source code to the final actual execution of the sequence of instructions, will undergo the following 3 kinds of reordering, as shown in

The above 1 belongs to the compiler reordering, and 2 and 3 are processor reordering. These reordering may cause memory visibility issues with multithreaded programs. In a single-threaded program, reordering of existing control-dependent operations does not alter the execution result (which is also why as-if-serial semantics allow for reordering of operations that have control dependencies), but in multithreaded programs, the execution of a control-dependent operation may change the result of a program.

1) Data dependencies (for individual processors only)

On reordering, one of the first concepts here is data dependency. If two operations access the same variable, and one of the two operations is a write operation, there is a data dependency between the two operations. Data dependencies are categorized into the following 3 types, as shown in the following table.

In the above 3 cases, the execution result of the program will be changed as long as the order of execution of the two operations is reordered. As mentioned earlier, the compiler and the processor may reorder the operations. When the compiler and processor are reordered, data dependencies are observed, and the compiler and processor do not change the order in which the two operations exist that have data dependencies. The data dependencies described here are only for sequences of instructions executed in a single processor and for operations performed in a single thread, and data dependencies between different processors and between different threads are not considered by the compiler and processor.

2) as-if-serial semantics

As-if-serial semantics means that the execution of (single-threaded) programs cannot be changed, no matter how they are reordered. The compiler, runtime, and processor must adhere to as-if-serial semantics.

To comply with as-if-serial semantics, compilers and processors do not reorder operations that have data dependencies. As-if-serial semantics protects a single-threaded process, and as-if-serial semantics makes it unnecessary for single-threaded programmers to worry about reordering, or to worry about memory visibility issues.

3) Happens-before

If the result of one operation needs to be visible to another, there must be a happens-before relationship between the two operations. The two actions mentioned here can be either within a thread or between different threads.

The specific definition of happens-before relationship is as follows.

① If one operation Happens-before another operation, the result of the first operation is visible to the second operation, and the first operation is executed in the order before the second operation.
② the existence of a happens-before relationship between the two operations does not imply that the specific implementation of the Java platform must be performed in the order specified by the Happens-before relationship. This reordering is not illegal (that is, JMM allows this sort of reordering) if the results of the execution after reordering are consistent with the results performed by the happens-before relationship.

The ① above is JMM's commitment to programmers. From the programmer's point of view, the happens-before relationship can be understood this way: if a happens-before B, then the Java memory model will assure the programmer that the results of the--a operation will be visible to B, and that A's order of execution precedes B. Note that this is just a guarantee from the Java memory model to the programmer! The above ② is the binding principle of JMM for compilers and handlers. As stated earlier, it is a fundamental principle to follow: as long as the execution results of the program are not changed (that is, single-threaded and correctly synchronized multithreaded programs), the compiler and processor can be optimized. Therefore, happens-before relationships are essentially the same as as-if-serial semantics.

as-if-serial semantics guarantees that the execution results of a single-threaded program are not changed, and that the happens-before relationship guarantees that the execution results of correctly synchronized multithreaded programs are not changed.
as-if-serial Semantics creates a mirage for programmers who write single-threaded programs: single-threaded procedures are executed in the order of the program. The Happens-before relationship creates a mirage for programmers who write correctly synchronized multithreaded programs that are executed in the order specified by Happens-before.
The purpose of as-if-serial semantics and Happens-before is to improve the degree of parallelism of program execution without changing the results of program execution.

The Happens-before rules are as follows:

Program Order rules: Each action in a thread is happens-before to any subsequent action in that thread.
Monitor lock rule: the unlocking of a lock is happens-before to the subsequent locking of the lock.
Volatile variable rule: writes to a volatile field, happens-before to any subsequent reading of this volatile field.
Transitivity: If a happens-before B, and B happens-before C, then a Happens-before c.
Start () rule: If thread A performs an operation Threadb.start () (boot thread B), then the Threadb.start () operation of the A thread happens-before any action in thread B.
Join () rule: If thread A performs an operation Threadb.join () and returns successfully, any action in thread B happens-before the successful return of thread A from the Threadb.join () operation.

Second, the example analysis

Assume that there are two threads that call writer () and reader () of the same test object, respectively. What is the value of B, please?

(a) 1
(b) 2
(c) 1 or 2

  Public classtest{Private BooleanFlag =false; Private intA = 0;  Public voidwriter () {a= 1; Flag=True; }     Public voidReader () {if(flag) {b= A + 1        }    }}

The main concern here is the processor reordering problem. The current processor is executed after reordering some of the instructions in order to speed up instruction execution.

Data dependency

Data dependency is a simple concept that determines whether the two lines of code have dependencies on the data. For example:

1                // (a)num2 = 2                // (b)result = num1 + num2  // (c)

Obviously, the NUM1 and num2 used in the C statement depend on A and b.

There are three types of data dependencies:

    • 1 store-load
    • 2 Load-store
    • 3 Store-store

How to determine whether there is dependency, very simple, only to determine whether the same variable between two statements, whether it is a write operation.

happen before

The JVM defines a concept called happen before, which means that the result of the previous execution is visible to the next execution. In short , the previous one is executed before the next one can be executed . However, in order to improve the processing speed, the JVM weakened the concept, in the case of data dependency, the previous one executes, can execute the latter.

Look at the following example:

1                // (a)num2 = 2                // (b)result = num1 + num2  // (c)

For the above three statements A, B, c execution, single-threaded sequential execution case.

a happen before b       b happen before c。

Based on transitivity, it can be concluded that:

a happen before c

The NUM1 and num2 used by C directives are obviously dependent on a and B, typically store-load. So the C directive must wait until A and B are complete before execution. However, A and B do not have data dependencies, and the JVM allows the processor to reorder A and B.

a -> b -> c = 3b -> a -> c = 3

So what exactly is happen before? My understanding is that happen before is the JVM's abstraction of the underlying memory control concept. We can judge the relationship of happen before according to the Order of code, and the JVM will perform different actions based on the actual situation (such as adding a memory barrier, a processor barrier, preventing reordering or not doing any extra work, allowing the processor to punch the order). This layer makes the memory control transparent to the programmer, and the programmer does not need to consider the actual execution of the code, and the JVM guarantees a single-threaded execution success, as-if-serial.

Since the JVM is already transparent with memory control, why this is clear is that the JVM only guarantees single-threaded execution success, and in a multithreaded environment, there will be a variety of problems.

Answer

Let's use the above-mentioned analysis of the original topic.

A thread execution:

    public void writer(){            a = 1;              // (1) flag = True; // (2) }

b Thread Execution:

    public void reader(){        if (flag){              // (3) b = a + 1 // (4) } }

1. Consider the situation that most people consider first:

Order of Instruction: (1), (2), (3), (4), b = 1 +1 = 2

2. Unexpected situations
For the A-thread, statements (1) and (2) do not have any data-dependency problems. The processor can therefore reorder it, which means that the instruction (2) may be executed before the instruction (1).
Then when the instruction is in order according to (2), (3), (4), (1), b = 0 +1 = 1

3. There is also a situation
For a B-thread, the processor might process (4) ahead of time, putting the result in Rob, and if the control statement (3) is true, the result is taken out of Rob directly, which is an optimization technique, forecast.
So the order of command execution may be (4)-X-->x

It seems that 4 statements are likely to be executed first.

To summarize, in a multiprocessor environment, because each processor has its own read-write buffer, it makes some data inconsistent. JMM will have a series of actions to ensure data consistency, but in the multi-threaded environment, there will still be a lot of weird problems occur, this time to consider the processor, compiler to reorder.

Iii. Summary of Knowledge points 1, order reordering
Most modern microprocessors use a method that executes commands in a disorderly order (Out-of-order execution, referred to as Oooe or Ooe),
Run a subsequent instruction that currently has the ability to execute immediately if the condition allows, bypassing the wait to get the data required for the next instruction.
The processor can greatly improve the execution efficiency through the technique of disorderly execution. In addition to the processor, the JIT compiler for the common Java Runtime Environment also does the command reordering operation, where the resulting machine instruction is inconsistent with the sequence of bytecode instructions.
2,as-if-serial semantics
As-if-serial semantics means that all actions can be reordered for optimization, but it is important to ensure that the results of their reordering are consistent with the expected results of the program code itself.
Java compilers, runtimes, and processors guarantee the as-if-serial semantics of a single thread.

PS: The instruction appears to be continuous and is a statement of the performance characteristics of this implementation.

To ensure this semantics, reordering does not occur in operations that have data dependencies .

3, memory access reordering and memory visibility
In a computer system, in order to avoid the time overhead of the processor accessing main memory as much as possible, the processor mostly uses the cache to improve performance. That is, the data in the cache is not synchronized with the main memory, and the data cached between each CPU (or CPU core) is not synchronized in real time. This causes the values of the data at the same memory address that each CPU sees at the same point in time may be inconsistent. From a procedural point of view, the values of shared variables that each thread sees may be inconsistent at the same time. Some views will also consider this phenomenon as a reordering, named "Memory system reordering." Because this memory visibility problem results in a reordering of memory access instructions.
(Execution does not know that execution is performed and that the reordering does not perform the same result)
4, memory access reordering with Java memory model
The goal of Java is to become a platform-agnostic language, write once, run anywhere. However, the rules of order reordering under different hardware environments are not the same. For example, a Java program that runs normally under x86 may get unintended results under IA64. To this end, JSR-1337 has developed the Java memory Model (JMM), which is designed to provide a unified and reference specification for masking platform differences. Starting with Java 5, the Java memory model becomes part of the Java language specification.

According to the Java memory model, the following rules can be summed up in several happens-before.

(PS: A memory model that unifies some of the visibility and reordering issues into a standard description through the runtime environment)

The front and back two operations of the Happens-before are not reordered and the latter is visible to the memory of the former.

Program Order rule:     every action A in a thread is happens-before to every action B in that thread, where all action B can appear after a in the program. Monitor lock rule:     unlocking a monitor lock happens-before every subsequent locking to the same monitor lock. Volatile variable rule: write Operations on volatile fields happens-before each subsequent read and write operation to the same domain. Thread start rule:     in one thread, the call to Thread.Start is happens-before the action of each startup thread. Thread termination rule: Any action in the thread is happens-before to other threads that the thread has been terminated, or successfully returned from the Thread.Join call, or Thread.isalive returns FALSE. Break rule:        One thread calls another thread's interrupt Happens-before the interrupted thread discovers the interrupt. Finalization rule: The        end of an object's constructor Happens-before at the beginning of the object's finalizer. Transitivity:          If a happens-before to B and B Happens-before to C, a happens-before to C

Happens-before relationship is just an approximation of the Java memory model , it is not rigorous enough, but it is easy to use for daily program development Reference,

For a more rigorous definition and description of the Java memory model, please read the JSR-133 original or Java Language Specification section 17.4.

In addition, the Java memory model extends the semantics of volatile and final.

The expansion of volatile semantics guarantees that volatile variables are not reordered in some cases, and the read and assignment operations of volatile 64-bit variables, double and long, are atomic. The extension of final semantics guarantees that all final member variables must be initialized (provided there is no overflow of this reference) before the method of constructing an object is finished.

(PS: No understanding of final meaning)

The Java memory model's provisions on reordering are summarized as shown in the following table. (PS: The following table is not read)

5, Memory barrier
A memory barrier (memory Barrier, or sometimes called an Fence) is a CPU instruction that controls reordering and memory visibility issues under certain conditions .

The Java compiler also prohibits reordering based on the rules of the memory barrier.

Memory barriers can be divided into the following types:

loadload  barrier : For such a statement Load1; Loadload; Load2, the data to be read by the LOAD1 is guaranteed to be read before Load2 and subsequent read operations are accessed. storestore barrier : For such a statement Store1; Storestore; Store2, the Store1 write operation is guaranteed to be visible to other processors before Store2 and subsequent write operations are performed. loadstore barrier : For such a statement Load1; Loadstore; Store2, the data to be read is guaranteed to be read by the Load1 before Store2 and subsequent write operations are brushed out.
storeload barrier : For such a statement Store1; Storeload; Load2, ensure that Store1 writes are visible to all processors before Load2 and all subsequent read operations are performed.
Its overhead is the largest of the four types of barriers. In most implementations of the processor, this barrier is a universal barrier that combines the functions of three other memory barriers.

Some processors have strict reordering rules, do not need a memory barrier to work well, theJava compiler will not place a memory barrier in this case.
In order to implement the JSR-133 described in the previous chapter, the Java compiler uses memory barriers in this way. (PS: The following table is not read)

Iv. Reference of the case

78221064

Java concurrent programming principle and Combat 41: reordering and Happens-before

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.