In-depth understanding of the Java memory Model (i)--basic

Source: Internet
Author: User
Tags flushes volatile

Classification of concurrent programming models

In concurrent programming, we need to deal with two key issues: how to communicate between threads and how threads are synchronized (the threads here refer to the activity entities that execute concurrently). Communication refers to the mechanism by which the threads exchange information. In imperative programming, there are two kinds of communication mechanisms between threads: Shared memory and message passing.

In the concurrency model of shared memory, the common state of the program is shared between threads, and the threads communicate implicitly through the public state in the write-read memory. In the concurrency model of message passing, there is no public state between threads, and the threads must communicate explicitly by sending messages explicitly.

Synchronization refers to the mechanism that a program uses to control the relative order of operations between different threads. In the shared-memory concurrency model, synchronization is performed explicitly. Programmers must explicitly specify a method or a piece of code that requires mutually exclusive execution between threads. In the concurrency model of message passing, synchronization is implicit because the message must be sent before the message is received.

Java concurrency is a shared-memory model, where communication between Java threads is always implicit and the entire communication process is completely transparent to the programmer. If a Java programmer writing a multithreaded program does not understand the working mechanism of implicit communication between threads, it is likely to encounter a variety of strange memory visibility issues.

Abstraction of the Java memory model

In Java, all instance fields, static fields, and array elements are stored in heap memory and heap memory is shared between threads (this article uses the term "shared variables" to refer to instance fields, static fields, and array elements). Local variables, method definition parameters (the Java language specification is called formal method parameters) and exception handler parameters (exception handler parameters) are not shared between threads. They do not have memory visibility issues and are not affected by the memory model.

Communication between Java threads is controlled by the Java memory model (this is referred to as JMM), and JMM determines when a thread's write to a shared variable is visible to another thread. From an abstract point of view, JMM defines the abstract relationship between threads and main memory: Shared variables between threads are stored in main memory, and each thread has a private local memory (local memory) in which the thread is stored in a copy of the read/write shared variable. Local memory is an abstract concept of JMM and is not really there. It covers caching, write buffers, registers, and other hardware and compiler optimizations. The Java memory model is abstracted as follows:

From the point of view, if you want to communicate between thread A and thread B, you have to go through the following 2 steps:

    1. First, thread a flushes the updated shared variables in local memory A to the main memory.
    2. Then, thread B goes to main memory to read shared variables that have been updated before thread A.

These two steps are described below:

As shown, local memory A and B have a copy of the shared variable x in main memory. Suppose initially, these three in-memory x values are 0. When thread a executes, it temporarily stores the updated X value (assuming a value of 1) in its own local memory a. When thread A and thread B need to communicate, thread a first flushes its local in-memory modified x value to main memory, when the X value in main memory becomes 1. Then, thread B goes to main memory to read the x value of the updated thread A, at which point the X value of the local memory of thread B also becomes 1.

Overall, these two steps are essentially thread a sending a message to thread B, and the communication process must go through main memory. JMM provides a memory visibility guarantee for Java programmers by controlling the interaction between main memory and the local memory of each thread.

Re-order

In order to improve performance when executing programs, the compiler and processor often reorder instructions. There are three types of reordering:

    1. The re-ordering of compiler optimizations. The compiler can reschedule the execution of a statement without changing the semantics of the single-threaded procedure.
    2. Reordering of instruction-level parallelism. Modern processors use instruction-level parallel technology (Instruction-level Parallelism, ILP) to overlap multiple instructions. If there is no data dependency, the processor can change the order in which the statement corresponds to the machine instruction execution.
    3. reordering of memory systems. Because the processor uses the cache and read/write buffers, this makes the load and storage operations appear to be in a disorderly sequence.

From the Java source code to the final actual execution of the sequence of instructions, the following three kinds of reordering are experienced:

The above 1 belongs to the compiler reordering, and 2 and 3 are processor reordering. These reordering can cause memory visibility issues with multithreaded programs. For compilers, the compiler-JMM of a compiler prevents a particular type of compiler from being reordered (not all compilers are forbidden). For processor reordering, the JMM collation of the handler will require the Java compiler to insert a specific type of memory barrier when generating the sequence of instructions (memories Barriers,intel called the fence) instruction, A memory barrier directive prevents a particular type of handler from being reordered (not all handlers are disabled).

JMM is a language-level memory model that ensures that programmers are guaranteed consistent memory visibility over different compilers and different processor platforms by prohibiting certain types of compilers from reordering and processing.

Processor reordering and memory barrier directives

Modern processors use write buffers to temporarily hold data written to memory. Write buffers ensure that the instruction pipeline runs continuously, and it avoids the delay that occurs when the processor pauses to wait for the data to be written to the memory. At the same time, you can reduce the memory bus footprint by flushing the write buffer in batches, and by combining multiple writes of the same memory address in the write buffer. Although write buffers have so many benefits, the write buffers on each processor are only visible to the processor on which it resides. This feature has an important impact on the order in which memory operations are executed: the order in which the processor performs read/write operations on memory is not necessarily the same as the order in which the memory actually occurs in the read/write operation! For specific instructions, see the following example:

Processor A Processor B
A = 1; A1
x = b; A2
b = 2; B1
y = A; B2
Initial state: a = b = 0
The processor allows the result to be executed: x = y = 0

Assuming that processor A and processor B perform memory accesses in parallel in the order of the program, the result of x = y = 0 may eventually be obtained. The specific reasons are as follows:

Here processor A and processor B can simultaneously write shared variables to their write buffers (A1,B1), then read another shared variable (A2,B2) from memory, and finally flush the dirty data stored in their write buffers into memory (A3,B3). When executed in this time series, the program can get the result of x = y = 0.

In the order in which the memory operation actually occurs, the write operation A1 is really executed until processor a executes A3 to refresh its write buffer. Although processor a performs memory operations in the order of: A1->A2, the actual order in which the memory operations actually occur is: A2->A1. At this point, the sequence of memory operations for processor A is reordered (the case of processor B is the same as processor a, which is not discussed here).

The key here is that because the write buffer is visible only to its own processor, it can cause the processor to perform memory operations in a sequence that is inconsistent with the actual order in which the memory is actually performed. Because modern processors use write buffers, modern processors allow for reordering of write-read operations.

The following is a list of the types of reordering that are allowed for common processors:

Load-load Load-store Store-store Store-load Data dependency
Sparc-tso N N N Y N
x86 N N N Y N
Ia64 Y Y Y Y N
Powerpc Y Y Y Y N

The "N" in the table cell indicates that the processor does not allow two operations to be reordered, and "Y" means that reordering is allowed.

As we can see from the table above, common processors allow store-load to be reordered, and common processors do not allow reordering of data-dependent operations. Sparc-tso and x86 have relatively strong processor memory models that allow only write-read operations to be reordered (because they all use write buffers).

※ Note 1:sparc-tso refers to the characteristics of the SPARC processor when running with the TSO (total Store order) memory model.

※ Note 2: The x86 in the table above includes x64 and AMD64.

※ Note 3: This article ignores the memory model of the ARM processor, which is very similar to the memory model of the PowerPC processor.

※ Note 4: The data dependencies will be described in this article.

To ensure memory visibility, the Java compiler inserts a memory barrier directive in place of the generated instruction sequence to suppress a particular type of handler reordering. JMM the memory barrier directives into the following four categories:

Barrier type instruction Example Description
Loadload barriers Load1; Loadload; Load2 Ensure the loading of the LOAD1 data, before the loading of the Load2 and all subsequent load instructions.
Storestore barriers Store1; Storestore; Store2 Ensure that the Store1 data is visible to other processors (flushed to memory), previously stored in Store2 and all subsequent storage instructions.
Loadstore barriers Load1; Loadstore; Store2 Ensure that the LOAD1 data is loaded before the Store2 and all subsequent storage instructions are flushed to memory.
Storeload barriers Store1; Storeload; Load2 Make sure that the Store1 data becomes visible to other processors (that is, flush to memory), before the loading of Load2 and all subsequent mount instructions. The Storeload barriers will make the memory access instruction after all memory access instructions (store and mount instructions) before the barrier is complete.

Storeload barriers is a "all-in-one" barrier that simultaneously has the effect of three other barriers. Most modern multiprocessor support this barrier (other types of barriers are not necessarily supported by all processors). The overhead of executing the barrier is expensive because the current processor typically flushes all the data in the write buffer into memory (buffer fully flush).

Happens-before

Starting with JDK5, Java uses the new JSR-133 memory model (this article, unless specifically stated, is for the JSR-133 memory model). JSR-133 presents the concept of Happens-before, which illustrates the memory visibility between operations. 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 Happens-before rules that are closely related to programmers 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 monitor lock is happens-before to the locking of the subsequent lock on the monitor.
    • 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.

Note that there is a happens-before relationship between the two operations, which does not mean that the previous operation must be performed before the next operation! Happens-before only requires that the previous operation (the result of execution) be visible to the latter operation, and that the previous operation is in order before the second operation (the first is visible to and ordered before the second). The definition of Happens-before is subtle, and the latter will specify why Happens-before is so defined.

The relationship between Happens-before and JMM is as follows:

As shown, a Happens-before rule typically corresponds to multiple compiler collation and handler reordering rules. For Java programmers, the Happens-before rules are easy to understand, avoiding the programmer to learn complex reordering rules and the specific implementations of these rules in order to understand the memory visibility that JMM provides.

In-depth understanding of the Java memory Model (i)--basic

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.