Java memory model

Source: Internet
Author: User
Tags finally block volatile

Question: In concurrent programming, what mechanisms are implemented between multiple threads for communication (information exchange), and what mechanisms are synchronizing data?
Answer: In the Java language, the shared memory model is used to realize the exchange of information and data synchronization between multiple threads.

By sharing the public state of the program between the threads, the implicit communication is done by means of the public state in the read-write memory. Synchronization refers to the mechanism by which a program controls the relative order in which programs are executed between multiple threads, and in the shared memory model, synchronization is explicit, and programmers must explicitly specify that a method/code block needs to be mutually exclusive between threads.

Overview

The main goal of the Java memory model is to define access rules for variables in the program, that is, the underlying details of storing variables in the JVM into memory and removing variables from memory. The variables here are different from those in Java programming, which contain instance fields, static fields, and elements that make up the array object, but do not contain local variables and method parameters, because the latter is thread-private and not shared. Of course there is no data competition problem (if the local variable is a reference reference type, the object it refers to can be shared by each thread in the Java heap, but the reference reference itself is thread-private in the local variable table of the Java stack). In order to achieve high performance, the Java memory model does not restrict the execution of specific registers or caches that use the processor to interact with the main memory, nor does it limit the immediate compiler's optimizations to adjust the order of code execution.

JMM Specifies that all variables are stored in the main memory. Each thread also has its own working memory (working memories), where the thread's working memory holds a copy of the main memory of the variable used by the thread, and all operations of the thread on the variable (read, assign, and so on) must be in working memory, Instead of directly reading and writing the variables in the main memory (the volatile variable still has a copy of the working memory, but because of its special sequence of operations, it looks like read-write access directly in main memory). There is no direct access to variables in the other's working memory between different threads, and the transfer of values between threads needs to be done through main memory.

Thread 1 and thread 2 The following steps are generally needed in order to exchange data:
1. Thread 1 Flushes the updated shared variables in work memory 1 to main memory.
2. Thread 2 to main memory to read thread 1 refreshed shared variables, and then copy a copy to work memory 2.

features of the memory model

The Java memory model is built around the three characteristics of atomicity, visibility, and ordering in concurrent programming, so let's look at these three features in turn:

atomicity (atomicity)

Atomicity means that an operation cannot be interrupted, either completely executed, or not executed. This is somewhat similar to a transactional operation, where all execution succeeds or is rolled back to the state before the operation was performed.

Basic type data access is mostly atomic, long and double variables are 64 bits, but in 32-bit JVMs, 32-bit JVM will divide the read and write operations of 64-bit data into 2-bit read-write operations, which results in a long, A variable of type Double is non-atomic in a 32-bit virtual machine, and the data can be corrupted, which means that multiple threads are not thread-safe while concurrently accessing it.

Let's demonstrate the problem of access to 64-bit long data under this 32-bit JVM:
"Code 1"

 Public  class notatomicity {    //static variable T     Public  Static Longt =0;The Get method of the//static variable T     Public  Static Long Gett() {returnT }set method for//static variable T     Public  Static void Sett(LongT) {notatomicity.t = t; }//Change the thread of the variable T     Public Static  class changet implements Runnable{        Private Longto; Public Changet(LongTo) { This. to = to; } Public void Run() {//constant setting of the long variable to T             while(true) {Notatomicity.sett (to);//Take the execution time fragment of the current thread out so that it can be determined by the thread scheduling mechanism which thread is capable of executingThread.yield (); }        }    }//Read the variable t thread, if the value read and set the value is inconsistent, it means that the data of the variable T is corrupted, that is, thread is unsafe     Public Static  class Readt implements Runnable{         Public void Run() {//constant reading of the value of notatomicity T             while(true) {LongTMP = Notatomicity.gett ();//comparison is one of the values you set                if(TMP! = -L && tmp! = $L && tmp! =- -L && tmp! =- -L) {//If the program executes here, it indicates that the long type variable T, whose data has been corruptedSYSTEM.OUT.PRINTLN (TMP); }////The execution time fragment of the current thread so that it can be re-determined by the thread scheduling mechanism which thread is capable of executingThread.yield (); }        }    } Public Static void Main(string[] args) {NewThread (NewChanget ( -L). Start ();NewThread (NewChanget ( $L). Start ();NewThread (NewChanget (- -L). Start ();NewThread (NewChanget (- -L). Start ();NewThread (NewReadt ()). Start (); }}

We created 4 threads to assign a long variable T to 100,200,-300,-400, one thread is responsible for reading the variable T, and if normal, the value of T is one of our assignments, but in the 32 JVM (PS: 64 of people don't think about it), things will be unexpected. If the program is normal, our console will not have any output, but in fact, when the program runs, the console will output the following information:

-42949670964294966896-4294967096-42949670964294966896

This occurs because, in a 32-bit JVM, the read and write of the 64-bit long data is not atomic, that is, it is not atomic and interferes with each other when it is concurrent.

In a 32-bit JVM, to guarantee the atomicity of the operation of a long, double type of data, you can synchronize the methods that access that data, just like this:
"Code 2"

 Public  class atomicity {    //static variable T     Public  Static Longt =0;//Static variable t get method, synchronous method     Public synchronized Static Long Gett() {returnT }//Static variable t set method, synchronous method     Public synchronized Static void Sett(LongT) {atomicity.t = t; }//Change the thread of the variable T     Public Static  class changet implements Runnable{        Private Longto; Public Changet(LongTo) { This. to = to; } Public void Run() {//constant setting of the long variable to T             while(true) {Atomicity.sett (to);//Take the execution time fragment of the current thread out so that it can be determined by the thread scheduling mechanism which thread is capable of executingThread.yield (); }        }    }//Read the variable t thread, if the value read and set the value is inconsistent, it means that the data of the variable T is corrupted, that is, thread is unsafe     Public Static  class Readt implements Runnable{         Public void Run() {//constant reading of the value of notatomicity T             while(true) {LongTMP = Atomicity.gett ();//comparison is one of the values you set                if(TMP! = -L && tmp! = $L && tmp! =- -L && tmp! =- -L) {//If the program executes here, it indicates that the long type variable T, whose data has been corruptedSYSTEM.OUT.PRINTLN (TMP); }////The execution time fragment of the current thread so that it can be re-determined by the thread scheduling mechanism which thread is capable of executingThread.yield (); }        }    } Public Static void Main(string[] args) {NewThread (NewChanget ( -L). Start ();NewThread (NewChanget ( $L). Start ();NewThread (NewChanget (- -L). Start ();NewThread (NewChanget (- -L). Start ();NewThread (NewReadt ()). Start (); }}

In doing so, the atomic nature of the 64-bit data operation can be guaranteed.

Visibility of

Once a thread has modified the shared variable, the other thread can immediately see (perceive) the variable (change).
The Java memory model implements visibility by synchronizing the modified values of variables in working memory to main memory, refreshing the latest values from the main memory to the working memory before reading the variables, and relying on the main memory.
This is true for both normal and volatile variables: the special rules of volatile ensure that the new value modified by the volatile variable value is immediately synchronized to the main memory and flushed from main memory every time the volatile variable is used. Therefore, volatile guarantees the visibility of the operation variables between multiple threads, while ordinary variables do not guarantee this.

In addition to the volatile keyword to achieve visibility, there are also synchronized,lock,final.

With the Synchronized keyword, when the synchronization method/synchronization block starts (Monitor Enter), when a shared variable is used, the variable value is flushed from the main memory to the working memory (that is, the latest value from the main memory to the thread-private working memory), at the end of the synchronization method/synchronization block ( Monitor Exit), synchronizes the value of the variable in the working memory into main memory (writes the value in the thread's private working memory to the main memory for synchronization).

The most common implementation of Reentrantlock (re-entry lock) using the lock interface is to achieve visibility: when we execute the Lock.lock () method at the beginning of the method, this has the same semantics as the Synchronized start position (Monitor Enter), Even if a shared variable is flushed from the main memory to the working memory (that is, the latest value is read from the main memory into the thread-private working memory), the Lock.unlock () method is executed in the final finally block of the method, and the synchronized end position (Monitor EXIT) has the same semantics, that is, the variable values in the working memory are synchronized to the main memory (the value in the working memory of the thread is written to the main memory for synchronization).

The final keyword visibility refers to: A final modified variable, once the number of constructors is initialized, and there is no reference to "this" in the constructor ("This" refers to escaping is very dangerous, other threads are likely to access to only "initialize half" through the reference Object), then other threads can see the value of the final variable.

Order of

For the code of a thread, we always assume that the code is executed from the point of the go, and then in turn. This cannot be said completely wrong, in a single-threaded program, it is true, but in multi-threaded concurrency, the execution of programs may appear disorderly order. One sentence can be summed up as: In this thread to observe, the operation is orderly; If you look at another thread in a thread, all operations are unordered. The first half of the sentence refers to "line range is expressed as serial semantics (within thread as-if-serial semantics)", the latter sentence refers to "instruction rearrangement" phenomenon and "working memory and main memory synchronization delay" phenomenon.

Java provides two keyword volatile and synchronized to ensure the order of operations between multithreading, the volatile keyword itself by adding a memory barrier to prohibit the reordering of instructions, The Synchronized keyword is implemented by a variable that allows only one thread to lock it at the same time, and in a single-threaded program, there is no "instruction Reflow" or "Working memory and Master Memory synchronization delay" phenomena, only in multithreaded programs.

Happens-before Principles

The order relationship between the two operations defined in the Java memory model, if operation a precedes action B, the effect of operation A can be observed by Action B, which includes modifying the values of shared variables in memory, sending messages, calling methods, and so on.

Here are some of the "natural" happens-before relationships under the Java memory model, which exist without any Synchronizer assistance and can be used directly in the encoding. If the relationship between the two operations is not there and cannot be deduced from the following rules, they are not guaranteed to be sequential, and the virtual machine can reorder them arbitrarily.

    1. Procedural Order rules (pragram order rule) : In a thread, in the order of program code, the preceding operation precedes the one written in the previous operation. It should be accurate to control the flow order rather than the program code order, because the branching and looping structures are considered.
    2. pipe lock rule : A unlock operation first occurs after the lock operation that faces the same lock. The same lock must be emphasized here, and the "back" refers to the chronological order of the time.
    3. volatile Variable rule : Write to a volatile variable precedes the read operation of the variable, where the "back" Also refers to the chronological order of time.
    4. thread Start Rule : the start () method of the thread object takes precedence over each action of this thread.
    5. Thread finally rules (thread termination rule) : All operations in a thread occur first in the termination detection of this thread, and we can end with the Thread.Join () method, The return value of thread.isalive () is detected by a segment that the thread has terminated execution.
    6. thread Interruption rule : The call to the thread interrupt () method occurs when the code of the interrupted thread detects that the interrupt event occurred. The thread.interrupted () method can be used to detect if an interrupt has occurred.
    7. object Finalization rule (Finalizer rule) : an initialization of an object (the completion of a construction method execution) occurs first at the beginning of its finalize () method.
    8. transitivity (transitivity) : If operation a precedes action B, Operation b precedes Operation C, it is possible to conclude that operation a precedes operation C.

An operation "time first" does not mean that the operation will be "antecedent", then if an operation "first occurrence" whether it can be deduced that the operation must be "time to occur"? is also not tenable, a typical example is the order reordering. So the order of time and the Happens-before principle has nothing to do with the basic, so the measurement of concurrency security issues must be based on the Happens-before principle.

Java memory model

Related Article

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.