Java Memory Model and java model

Source: Internet
Author: User

Java Memory Model and java model
Model classification of basic concurrent programming

Two key issues to be addressed in concurrent programming are: how to communicate between threads and how to synchronize between threads.

Communication

Communication refers to the mechanism by which threads exchange information. In imperative programming, there are two communication mechanisms between threads: Shared Memory and message transmission.

In the concurrency model of shared memory, threads share the public state of the program, and threads implicitly communicate through the public state in the write-read memory.

In the message transmission concurrency model, there is no public state between threads, and the threads must explicitly send messages for explicit communication.

Synchronization

Synchronization refers to the mechanism by which a program is used to control the relative sequence of operations between different threads.

In the concurrency model of shared memory, synchronization is performed explicitly. The programmer must explicitly specify a method or code segment to be mutually exclusive between threads.

In the message transmission concurrency model, because the message must be sent before receiving the message, the synchronization is implemented implicitly.

Java concurrency uses the shared memory model. Communication between Java threads is always conducted implicitly, and the entire communication process is completely transparent to programmers.

JAVA Memory Model Abstraction

In Java, all instance domains, static domains, and array elements are stored in heap memory, and threads in the heap are shared. Local variables, method-defined parameters, and abnormal processor parameters are not shared between threads. They do not have memory visibility issues or are not affected by the memory model.

Communication between Java threads is controlled by the Java Memory Model (JMM. JMM determines when a thread writes shared variables to another thread. From an abstract point of view, JMM defines the abstract relationship between the thread and the main memory: Shared variables between threads are stored in the main memory, and each thread has its own private local memory, the local memory stores a copy of the shared variable to read/write. Local memory is an abstract concept of JMM and does not exist.

JMM Abstraction:

From the perspective, if thread A and thread B need to communicate, the following two steps are required:

1. Thread A needs to refresh the shared variable copy in local memory A to the master memory

2. Thread B reads the shared variables updated before thread A from the main memory.

Steps:

For example:

Local Memory A and B have copies of the primary memory shared variable X. Assume that the values of X in the three memories are 0 at the beginning. When thread A is executing, it temporarily stores the updated X value (assumed as 1) in its local memory. When thread A and thread B need to communicate, thread A will first refresh the modified X value in its local memory A to the main memory. At this time, the X value in the main memory is changed to 1. Then, thread B reads the value of shared variable X after thread A is updated to the main memory. At this time, the value of X in the local memory of thread B is also changed to 1.

The two steps are essentially sending messages to thread B after thread A, and the communication process must go through the main memory. By controlling the interaction between the main memory and the local memory of each thread, JMM provides memory visibility for Java programmers.

Reorder

To improve performance, compilers and processors often reorder commands when executing programs. There are three types of re-sorting:

1. Re-sorting optimized by the compiler. The compiler can reschedule the statement execution sequence without changing the semantics of a Single-threaded program.

2. command-level parallel re-sorting. Modern processors use the command-level parallel technology to execute multiple commands in parallel. If there is no data dependency, the processor can change the execution sequence of the statements corresponding to the machine commands.

3. Memory System re-sorting. Because the processor uses cache and read/write buffer, loading and storage operations may appear to be executed in disorder.

From the Java source code to the actual execution of the command sequence, there are three types of re-sorting:

The above re-sorting may cause memory visibility problems in multi-thread programs. For the compiler, the JMM compiler re-sorting rules disable specific types of compiler re-sorting (not all compiler re-sorting must be disabled ). For processor re-sorting, JMM's processor re-sorting rules require the Java compiler to insert a specific type of memory barrier command when generating the command sequence, the memory barrier command is used to prohibit specific types of processors from being reordered (not all processors are reordered ).

JMM is a language-level memory model. It ensures that different compilers and different processor platforms prohibit specific types of compilers from being reordered and processors being reordered, provides consistent memory visibility for programmers.

Processor re-sorting

Modern processors use a write buffer to temporarily save data written to memory. The write buffer can ensure that the command line runs continuously. It can avoid the delay caused by the pause of the processor and waiting for data writing to the memory. At the same time, you can refresh the write buffer in batches and Merge multiple writes to the same memory address in the write buffer to reduce the consumption of the memory bus. Although the write buffer has so many advantages, the write buffer on each processor is only visible to the processor where it is located. This feature will have an important impact on the execution sequence of memory operations: the execution sequence of the processor's read/write operations on the memory is not necessarily the same as the actual read/write operation sequence in the memory!

For example:

Assume that processor A and processor B execute memory access in parallel in the order of the program, but eventually the result may be x = y = 0. The specific causes are as follows:

Both processor A and processor B write the shared variables to the write buffer (A1, B1), and then read another shared variable (A2, B2) from the memory ), at last, the dirty data stored in the write buffer is refreshed to the memory (A3, B3 ). When executed in this time series, the program can get the result of x = y = 0.

From the actual memory operation sequence, until processor A executes A3 to refresh its write cache, write operation A1 is actually executed. Although processor A executes memory operations in the order of A1-> A2, the actual memory operations occur in the order of A2-> A1. In this case, the memory operation sequence of processor A is reordered.

The key here is that since the write buffer is only visible to the processor, it may cause the processor to execute memory operations in a different order from the actual operation sequence in the memory. Since modern processors all use the write buffer, modern processors allow reordering of write-read operations.

Memory barrier commands

To ensure memory visibility, the Java compiler inserts memory barrier commands to prevent specific types of processors from being reordered at the proper location where the command sequence is generated. JMM classifies memory barrier commands into the following four categories:

Barrier type Command example Description
LoadLoad Barriers Load1; LoadLoad; Load2 Ensure the loading of Load1 data, which is earlier than Load2 and all subsequent loading commands.
StoreStore Barriers Store1; StoreStore; Store2 Make sure that Store1 data is visible to other processors (refresh to memory) and stored in Store2 and all subsequent storage commands.
LoadStore Barriers Load1; LoadStore; Store2 Ensure that Load1 data is loaded, and all subsequent storage commands in Store2 are refreshed to the memory.
StoreLoad Barriers Store1; StoreLoad; Load2 Make sure that Store1 data is visible to other processors (New to memory), prior to Load2 and all subsequent loading commands. StoreLoadBarriers executes memory access commands after all memory access commands (storage and loading commands) before the barrier is implemented.
HAPPENS-BEFORE

The JSR-133 memory model uses the happens-before concept to describe memory visibility between operations. In JMM, if the result of an operation needs to be visible to another operation, there must be a happens-before relationship between the two operations. The two operations mentioned here can be within one thread or between different threads.

The following are the rules that are closely related to programmers:

  • Program sequence rules: any subsequent operations of happens-before in a thread.
  • Monitor lock rules: unlock a monitor. happens-before then locks the monitor.
  • Volatile variable rules: Write a volatile domain, and happens-before reads the volatile domain in any future.
  • Transmission: If A happens-before B and B happens-before C, then A happens-before C.

Note that there is a happens-before relationship between the two operations, which does not mean that the previous operation must be executed before the next operation! Happens-before only requires the previous operation (result of execution) to be visible to the next operation, the first operation is prior to the second operation in order (the first is visible to and ordered before the second ).

Shows the relationship between happens-before and JMM:

As shown in, a happens-before rule corresponds to one or more compiler and processor reordering rules.

Data Dependency

If two operations access the same variable and one of the two operations is a write operation, there is data dependency between the two operations. There are three types of Data Dependencies:

Name Sample Code Description
Post-read A = 1; B =; Write a variable and read it again.
Write after writing A = 1; a = 2; Write a variable before writing it.
Write after reading A = B; B = 1; Read a variable and write it again.

In the above three cases, the execution result of the program will be changed as long as the execution sequence of the two operations is re-ordered.

As mentioned above, the compiler and processor may re-Sort operations. The compiler and the processor follow the data dependency when re-sorting. the compiler and the processor do not change the execution sequence of the two operations with data dependency.

Note that the data dependency mentioned here is only applicable to the command sequence executed in a single processor and the operations executed in a single thread, data dependencies between different processors and between different threads are not considered by compilers and processors.

AS-IF-SERIAL Semantics

The as-if-serial semantics means that the execution results of (single thread) programs cannot be changed no matter how the compiler and the processor re-order (to improve the degree of parallelism. The compiler, runtime, and processor must comply with the as-if-serial syntax.

To comply with the as-if-serial compiler and processor, operations with data dependencies are not reordered, because such re-sorting changes the execution result. However, if there is no data dependency between operations, these operations may be reordered by the compiler and the processor.

For example:

double pi = 3.14;     //Adouble r  = 1.0;       //Bdouble area = pi * r * r;     //C

The data dependency of the preceding three operations is shown in:

As shown in, data dependency exists between A and C, and between B and C. Therefore, in the final execution of the command sequence, C cannot be reordered to the front of A and B (C to the front of A and B, and the program results will be changed ). However, there is no data dependency between A and B, and the compiler and processor can re-sort the execution sequence between A and B. Is the two execution sequence of the program:

In computer systems, software and hardware technologies share a common goal: to develop concurrency as much as possible without changing the execution results of programs. The compiler and processor follow this goal. From the definition of happens-before, we can see that JMM also follows this goal.

Influence of reordering on Multithreading

Example:

class Demo {  int a = 0;  boolean flag = false;  public void write() {    a = 1;//1    flag = true;//2  }  public void read() {    if(flag) {//3      int i = a * a;//4    }  }}

Since operations 1 and 2 do not have data dependencies, the compiler and the processor can reorder these two operations. Operations 3 and 4 do not have data dependencies, the compiler and the processor can also reorder these two operations.

1. What will happen when operations 1 and 2 are re-ordered?

As shown in, operations 1 and 2 are reordered. During program execution, thread A first writes the flag variable, and then thread B reads the variable. Because the condition is true, thread B reads variable. At this point, variable a is not written by thread A at all. Here, the semantics of the multi-threaded program is damaged by the re-sorting!

2. What is the effect of operation 3 and operation 4 on re-sorting (with this re-sorting, the dependency can be controlled by the way ).

In a program, Operations 3 and 4 have control dependencies. When there is a control dependency in the Code, the concurrency of the command sequence execution is affected. To this end, the compiler and the processor will use the speculative execution to overcome the impact of controlling the degree of parallelism on the degree of parallelism. Taking the speculative execution of the processor as an example, the processor of the execution thread B can read and calculate a * a in advance, and then temporarily Save the computing result to a reorder buffer (reorder buffer ROB) hardware cache. When the condition in operation 3 is true, the calculation result is written to variable I.

We can see that the speculative execution actually sorts operations 3 and 4. Here, the meaning of the multi-threaded program is broken!

In a single-threaded program, reordering operations with control dependencies exists, the execution results will not be changed (this is also the reason why the as-if-serial semantics allows re-sorting of operations with control dependencies); but in multi-threaded programs, re-sorting operations with control dependencies may change the execution results of programs.

Sequential consistency, sequential consistency, Memory Model

The sequential consistency memory model has two features:

  • All operations in a thread must be executed in the program order.
  • (Whether the program is synchronized or not) All threads can only see a single operation execution sequence. In the sequential consistency memory model, each operation must be performed atomically and immediately visible to all threads.

The sequential consistency memory model provides the following views for programmers:

In terms of concept, the sequence consistency model has a single global memory, which can be connected to any thread through a left-right swing switch, at the same time, each thread must execute memory read/write operations in the program order. From the above we can see that at any point in time, only one thread can be connected to the memory. When multiple threads run concurrently, the switch device in the figure serializes all memory read/write operations of all threads.

For example:

Assume that two threads A and B are concurrently executed. Among them, thread A has three operations, and their order in the program is A1-> A2-> A3. Thread B also has three operations in the program in the order B1-> B2-> B3.

Assume that the two threads use the monitor lock for proper synchronization: after the three operations of thread A are executed, the monitor lock is released, and then thread B obtains the same monitor lock. The execution effect of the program in the sequence consistency model is shown in:

Now let's assume that the two threads are not synchronized. The following is the execution of the unsynchronized program in the sequence consistency model:

In the sequence consistency model, although the overall execution sequence of the unsynchronized program is unordered, only one consistent sequence of execution can be seen in all threads. For example, the execution sequence of threads A and B is B1-> A1-> A2-> B2-> A3-> B3. This guarantee is obtained because every operation in the sequential consistency memory model must be immediately visible to any thread.

However, this guarantee is not provided in JMM. In JMM, the execution sequence of the unsynchronized program is unordered, and the operation execution sequence seen by all threads may be inconsistent. For example, before the current thread caches written data in the local memory and refreshes the data to the main memory, this write operation is only visible to the current thread; from the perspective of other threads, we will think that this write operation has not been executed by the current thread. This write operation can be visible to other threads only after the current thread refreshes the data written in the local memory to the main memory. In this case, the execution sequence of the current thread and other threads is inconsistent.

Sequence consistency of synchronization programs

The following describes how to use locks for synchronization of the preceding example program to see how the correctly synchronized program has sequential consistency.

See the following sample code:

Class demo {int a = 0; boolean flag = false; public synchronized void write () {// get lock a = 1; flag = true ;} // release the lock public synchronized void read () {// obtain the lock if (flag) {int I = a ;}/// release the lock}

In the above sample code, assume that thread A executes the reade () method after the write () method is executed. This is a correctly synchronized multi-threaded program. According to the JMM specification, the execution result of the program is the same as that of the program in the sequence consistency model. The following is a comparison of the execution sequence of the program in two memory models:

In the sequence consistency model, all operations are executed in the program order. In JMM, codes in the critical section can be reordered (however, JMM does not allow codes in the critical section to "escape" outside the critical section, which will damage the semantics of the Monitor ). JMM will perform some special processing at the exit critical section and the entry critical section, so that the thread has the same memory view as the sequential consistency model at these two time points. Although thread A performs re-sorting in the critical section, due to the mutex execution of the monitor, thread B cannot "Observe" the re-sorting of thread A in the critical section. This sort improves the execution efficiency without changing the execution results of the program.

Here we can see the basic principle of JMM in specific implementation: without changing (correctly synchronized) the execution results of the program, we try to open the door to the optimization of the compiler and processor.

Execution features of unsynchronized programs

The execution results of unsynchronized programs in JMM are unordered and unpredictable. The execution characteristics of the unsynchronized program in the two models are different as follows:

The third difference is closely related to the working mechanism of the processor bus. In a computer, data is transmitted between the processor and memory through the bus. Each data transfer between the processor and the memory is completed through bus transactions. Bus transactions include read transactions and write transactions. A read transaction transfers data from the memory to the processor, and a write transaction transfers data from the processor to the memory. Each transaction reads or writes one or more physical consecutive words in the memory. The bus synchronizes transactions that attempt to use the bus concurrently. During a processor's execution of bus transactions, the bus prohibits all other processors and I/O devices from executing read/write memory.

Bus working mechanism:

As shown in, if the processor A, B, and C initiate A bus transaction to the bus at the same time, then the bus arbitration will decide on the competition, assume that the bus determines that processor A wins the competition after arbitration (bus arbitration ensures that all processors can access the memory fairly ). At this time, processor A continues its bus transaction, while the other two processors need to wait until the bus transaction of processor A is completed before re-execution of memory access. Assume that during the execution of bus transactions by processor A (whether the bus transaction is A read transaction or write transaction), processor D initiates A bus transaction to the bus, in this case, the request of processor D is forbidden by the bus.

These bus mechanisms allow all processors to access the memory in a serialized manner. At any point in time, only one processor can access the memory. This feature ensures that memory read/write operations in a single bus transaction are atomic.

On some 32-bit processors, if write operations on 64-bit data are required to be atomic, there will be a large overhead. To Take Care Of This processor, the Java language specification encourages but does not force JVM to write 64-bit long variables and double variables with atomicity. When the JVM runs on such a processor, it splits the write operation of a 64-bit long/double variable into two 32-bit write operations for execution. These two 32-bit write operations may be assigned to different bus transactions for execution. At this time, writing to this 64-bit variable is not atomic.

When a single memory operation is not atomic, unexpected consequences may occur. See the following:

As shown in, assume that processor A writes A long variable and processor B needs to read this long variable. 64-bit write operations in processor A are split into two 32-bit write operations, and these two 32-bit write operations are allocated to different write transactions for execution. At the same time, 64-bit read operations in processor B are allocated to a single read transaction for execution. When processor A and processor B are executed in chronological order, processor B will see an invalid value that is "half written" by processor.

Note: In the old memory model before JSR-133, A read/write operation for a 64-bit long/double variable can be split into two 32-bit read/write operations for execution. Starting from the JSR-133 Memory Model (I .e. starting from JDK5), only write operations for a 64-bit long/double variable can be split into two 32-bit write operations, any read operation must be atomic in JSR-133 (that is, any read operation must be executed in a single read transaction ).

VolatileVOLATILE features

For example:

Public class VolatileTest {volatile long a = 1L; // use volatile to declare a 64-bit long public void set (long l) {a = l; // write a single volatile variable} public long get () {return a; // read a single volatile variable} public void getAndIncreament () {a ++; // read/write composite (multiple) volatile variables }}

Assume that multiple threads call the three methods of the above program respectively. This program is semantically equivalent to the following program:

Public class VolatileTest {long a = 1L; // 64-bit long common variable public synchronized void set (long l) {// synchronize a = l with the same lock for writing a single common variable;} public synchronized long get () {// use the same lock to synchronize return a;} public void getAndIncreament () {// call long temp = get () for reading a single common variable (); // call the synchronized read method temp + = 1L; // set (temp) for normal write operations; // call the synchronized write method }}

As shown in the preceding example, a single read/write operation on a volatile variable is synchronized with a read/write operation on a common variable using the same lock, they have the same execution effect.

The happens-before rule of the lock ensures the memory visibility between the two threads that release the lock and obtain the lock. This means that the read of a volatile variable is always visible (any thread) write the final content of the volatile variable.

The semantics of the lock determines that the code execution in the critical section is atomic. This means that even 64-bit long and double variables, as long as they are volatile variables, the read and write operations on the variables will be atomic. For multiple volatile operations or compound operations similar to volatile ++, these operations are not atomic in general.

In short, the volatile variable has the following features:

  • Visibility. The read of a volatile variable always shows (any thread) The Final write to this volatile variable.
  • Atomicity: read/write operations on any single volatile variable are atomic, but composite operations similar to volatile ++ are not atomic.
VOLATILE write-Read Memory Definition
  • When writing a volatile variable, JMM refreshes the shared variable value in the local memory corresponding to the thread to the main memory.
  • When a volatile variable is read, JMM sets the local memory corresponding to this thread to invalid. The thread will then read the shared variable from the main memory.

Assume that the above program flag variable is modified with volatile

VOLATILE memory semantics implementation

The following table lists the volatile reordering rules set by JMM for the compiler:

To implement volatile memory semantics, the compiler inserts a memory barrier into the instruction sequence when generating bytecode to prohibit specific types of processors from being reordered.

The following is a JMM memory barrier insertion policy based on a conservative policy:

  • Insert a StoreStore barrier before 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.

The following is the sequence of commands generated after volatile write operations Insert the memory barrier under the Conservative policy:

The following is the instruction sequence generated after the volatile read operation is inserted into the memory barrier under the Conservative policy:

The memory barrier insertion policies for the above volatile write operations and volatile read operations are very conservative. In actual execution, the compiler can omit unnecessary barrier as long as the volatile write-Read Memory semantics is not changed.

Memory semantics of lock release and Acquisition

When the thread releases the lock, JMM refreshes the shared variables in the local memory corresponding to the thread to the main memory.

When the thread gets the lock, JMM will invalidate the local memory corresponding to the thread. So that the code in the critical section protected by the monitor must read the shared variable from the main memory.

Implementation of lock memory Semantics

With the help of ReentrantLock to explain, PS: Later specifically talk about this (ReentrantLock, Synchronized, fair lock, unfair lock, AQS, etc.), you can look at the Big Brother's blog: http://cmsblogs.com /? P = 2210

CONCURRENT package implementation

If we carefully analyze the source code implementation of the concurrent package, we will find a universal implementation mode:

AQS, non-blocking data structures and atomic variable classes (java. util. concurrent. classes in the atomic Package). These basic classes in the concurrent package are implemented using this mode, and the high-level classes in the concurrent package depend on these basic classes. The concurrent package is implemented as follows:

Final

For final domains, the compiler and processor must follow two reordering rules:

Rewrite Rules for FINAL Fields

Re-sorting rules for writing final fields do not allow re-sorting of final fields beyond the constructor. The implementation of this rule includes the following two aspects:

  • JMM prohibits the compiler from sorting the write duplicates of the final domain outside the constructor.
  • The compiler inserts a StoreStore barrier after writing the final domain and before the constructor returns. This barrier prohibits the processor from sorting the write duplicates of the final domain outside the constructor.
Re-sorting rules for read FINAL Domains

In a thread, JMM prohibits the operation of the final domain contained in the first read object reference and the first read object (note that this rule is only applicable to the processor ). The compiler inserts a LoadLoad barrier before the final domain operation.

The FINAL domain is of the reference type.

For reference types, the re-sorting rules for writing final domains Add the following constraints to the compiler and processor:

Write the member fields of an object referenced by final in the constructor, and assign the reference of the constructed object to a reference variable outside the constructor, the two operations cannot be reordered.

Summarize the relationship between JMM, processor memory model, and sequential consistency memory model.

JMM is a language-level memory model. The processor memory model is a hardware-level memory model. The sequential consistency memory model is a theoretical reference model. The following is a comparison of the language memory model, processor memory model, and sequential consistency Memory Model:

JMM Design

JMM memory visibility guarantee

Java program memory visibility can be classified into the following three categories by program type:

1. Single-threaded program. Single-threaded programs do not have memory visibility issues. The compiler, runtime, and processor jointly ensure that the execution results of a Single-threaded program are the same as those of the program in the sequence consistency model.

2. Correctly synchronized multi-threaded programs. The execution of a correctly synchronized multi-threaded program will be sequential (the execution result of the program is the same as that of the program in the sequential consistency Memory Model ). This is the focus of JMM. JMM provides memory visibility for programmers by limiting the reordering of compilers and processors.

3. Non-synchronized/incorrectly synchronized multi-threaded programs. JMM provides them with the minimum security guarantee: The value read during thread execution, either the value written by a previous thread or the default value (0, null, false ).

Demonstrate the similarities and differences between the execution results of these three types of programs in JMM and in the sequential consistency Memory Model:

I have a public account, and I often share some Java-related dry goods;

If you like my share, you can search for "Java head" or "javatuanzhang" to follow up.

 

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.