Java Concurrency Programming (v) JVM command rearrangement

Source: Internet
Author: User
Tags visibility volatile

Introduction: In Java the seemingly sequential code in the JVM, the compiler or CPU may appear to reorder these operations instructions, in particular cases, the command reflow will give our program to bring uncertain results ....

What are the possible results of the following code?

 Public classpossiblereordering {Static intx = 0, y = 0;Static intA = 0, b = 0;  Public Static voidMain (string[] args)throwsinterruptedexception {Thread one=NewThread (NewRunnable () { Public voidRun () {a= 1; X=b;    }    }); Thread Other=NewThread (NewRunnable () { Public voidrun () {b= 1; Y=A;    }    });    One.start (); Other.start ();    One.join (); Other.join (); System.out.println ("("+ x + "," + y + ")");}

After reading this article you will understand.

What is command reflow?

The sequence of instructions formed after the program compiler compiles the order in which the instructions are executed by the computer, in general, the sequence of instructions outputs the determined result, to ensure that each execution has a definite result. However, in general, the CPU and compiler are allowed to optimize the instruction according to certain rules in order to improve the efficiency of the program execution.

The question is, why does rearrangement improve the efficiency of code execution?

In some cases, this optimization can lead to some logic problems of execution, mainly because there is a certain order between the code logic, in the concurrent execution, there will be two semantics, that is, according to different execution logic, you will get different results information.

Data dependencies

The main point is that the order between the different program directives is not allowed to interact, it can be said that there is data dependency between these program directives.

Which directives do not allow rearrangement?


The main examples are as follows:

The name       code example         demonstrates write-  after reading     a = 1;b = A;    After you write a variable, read the position again. Write  after write     a = 1;a = 2;    After writing a variable, write the variable again.  read after write     a = B;b = 1;    After reading a variable, write the variable.

In the analysis, it is found that there are write operations in each set of instructions, the location of this write operation is not allowed to change, otherwise it will bring a different execution result.
The compiler will not rearrange the program directives that have data dependencies, where the dependencies are only data dependencies in single-threaded situations , and this rule is invalidated in the case of multithreaded concurrency.

As-if-serial semantics

No matter how the reordering (compilers and processors in order to improve parallelism), the execution results of a single-threaded process cannot be changed. Compilers, runtime, and processors must adhere to as-if-serial semantics.
Analysis: Keywords are single-threaded cases that must be observed, and the rest are not observed.

What is as-if-serial semantics?

As-if-serial semantics means that all action 5 can be reordered for optimization, but must ensure that the results of their reordering are consistent with the expected results of the program code itself.


code example:

double pi = 3.14;         // A double r = 1.0;           // B Double // C

Analysis Code:

A->c b->c; There is no dependency between A and B, so in a single-threaded case, the order of the orders of a and a is can be re-ordered, C does not allow rearrangement, must be after a and b.
Summary of the conclusion:
As-if-serial semantics protects a single-threaded program, adheres to the as-if-serial semantic compiler, and the runtime and the processor create an illusion for programmers who write single-threaded programs: single-threaded procedures are executed in the order of the program. As-if-serial semantics make it unnecessary for single-threaded programmers to worry about reordering, or to worry about memory visibility issues.

Core points are single-threaded and do not adhere to this principle in multithreaded situations.

Command rearrangement under multi-threading

First we analyze based on an example of a piece of code, in the case of multithreading, whether the rearrangement has different results information:

classReorderexample {intA = 0; BooleanFlag =false;  Public voidwriter () {a= 1;//1Flag =true;//2    }       Public voidReader () {if(flag) {//3            inti = A * A;//4        }    }}

The above code, in single-threaded case, the execution result is OK, flag=true will be seen in the method body of reader, and set the result correctly. But in the case of multithreading, is there still only one definite result?
Assuming that A and b two threads execute the code fragment at the same time, the two possible execution processes are as follows:


Possible process 1, because there is no data dependency between the 1 and 2 statements, so the two can be reflow, the possible order between two threads is as follows:

Possible process 2: The execution order of statements between two threads is as follows:

According to Happens-before's program order rules, there are three happens-before relationships in the sample code that calculates the area of a circle:

What is happens-before relationship?


A Happens-before B;
B Happens-before C;
A Happens-before C;
The 3rd Happens-before relationship here is derived from the transitive nature of the happens-before.

What is control dependency?

What is speculation (speculation)?

In the program, Action 3 and Operation 4 exist to control dependencies. When there is a control dependency in the code, it affects the degree of parallelism that the instruction sequence executes. To do this, the compiler and processor use guessing (speculation) execution to overcome the effect of control affinity on the degree of parallelism. Taking the processor's guess execution as an example, the processor executing thread B can read and calculate the a*a in advance, and then temporarily save the results to a hardware cache called the reorder buffer (ROB). When the condition of the next operation 3 is judged to be true, the result of the calculation is written to the variable i. As we can see, guessing execution essentially reordering operations 3 and 4. Reordering here destroys the semantics of multi-threaded threads.

Similar to the example above, there are:

In thread A:

context =true;
In thread B:
 while (!inited) {     // based on the modification of the inited variable in thread A determines whether to use the context variable    sleep ();} dosomethingwithconfig (context) ;
Assume that a command reordering occurs in thread A:
True= Loadcontext ();
Then in B it is possible to get a context that has not yet been initialized or has not yet been initialized, causing a program error.

Example of a single mode failure that causes double locking to be re-ranked

Example 2: Command rearrangement results in a single-case mode failure
We all know a classic lazy loading method for double-judging a singleton mode:

 Public classSingleton {Private StaticSingleton instance =NULL; PrivateSingleton () {} Public StaticSingleton getinstance () {if(Instance = =NULL) {synchronzied (Singleton.class) {                if(Instance = =NULL) {instance=NewSingleton ();//Non-atomic operation                }            }        }        returninstance; }}
A seemingly simple assignment statement: instance= new Singleton (), but it is not an atomic operation, it can actually be abstracted as a few JVM directives:
Memory =allocate ();       //  ctorinstance (memory);     //  instance =memory;         // 3: Set instance to point to the memory address just allocated
The above operation 2 depends on Operation 1, but the operation 3 is not dependent on the Operation 2, so the JVM can be ordered for their optimization reordering, after reordering as follows:
Memory =allocate ();     //  instance =memory;       // 3:instance points to the memory address just allocated, when the object is not initialized ctorinstance (memory);   // 2: Initializing objects
After you see the command reflow, instance points to the allocated memory in front of it, and the memory initialization is placed behind it.
Thread A executes the assignment statement and assigns it to the instance reference before initializing the allocated object, just as another thread enters the method to determine that the instance reference is not NULL and then returns it to use, resulting in an error.

Solution: The inited in Example 1 and the instance in example 2 are modified with the keyword volatile, which prevents the JVM from ordering its associated code so that it can be executed in a given order .

The core point is the critical area between the two threads executing the same piece of code, sharing the variables between different threads, and the execution order, the CPU compiler's optimization of the program instructions, and so on, resulting in indeterminate execution results.

I feel that most of this happens in a multi-threaded environment: It's a good idea to add a volatile to the object you want to judge before you decide what to do and then make an error.

How to prevent command rearrangement

The volatile keyword guarantees the visibility of the variable, because the operation of volatile is in main memory, and the main memory is shared by all threads, where the cost is sacrificing performance, not using registers or caches, because they are not global, Visibility cannot be guaranteed and dirty reads can occur.

Volatile also has a function of local blocking of the occurrence of reordering (after JDK1.5, you can use volatile variables to suppress command reordering), the action instructions for volatile variables are not reordered, because if the reordering, it may produce visibility problems.

Locks (including explicit locks, object locks) and read-write to atomic variables ensure the visibility of variables in terms of guaranteed visibility.

However, the implementation is slightly different, such as a synchronous lock to ensure that the lock is re-read from memory into the data refresh cache, release the lock when the data is written back to memory to ensure that the data is visible, and the volatile variable is simply read and write memory.

The volatile keyword prevents instructions from being reordered by providing a "memory barrier", in order to implement volatile memory semantics, the compiler inserts a memory barrier in the instruction sequence to suppress a particular type of handler reordering when generating bytecode.
Most processors support memory barrier directives.
For compilers, it is almost impossible to find an optimal placement to minimize the total number of insert barriers, and the Java memory model takes a conservative approach. The following is a conservative policy-based JMM memory barrier insertion strategy:
Insert a storestore barrier in front of each volatile write operation.
Insert a storeload barrier behind each volatile write operation.
Insert a loadload barrier behind each volatile read operation.
Insert a loadstore barrier behind each volatile read operation.

Java Concurrency Programming (v) JVM command rearrangement

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.