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 engine from using the processor's specific registers or caches to interact with the main memory, nor does it limit the optimizations that the immediate compiler can use 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.
Example: Thread 1 and Thread 2 The following steps are typically needed to exchange data:
(1) Thread 1 flushes the updated shared variables in the working memory 1 to the 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.
Atomic Nature
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.
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
Java memory model