Java Memory Model (reprint)

Source: Internet
Author: User
Tags flushes mutex volatile

1. Overview

Multitasking and high concurrency are one of the most important indicators of the ability to measure a computer's processor. A general measure of the performance of a server, the use of transactions per second (transactions per Second,tps) This indicator comparison can explain the problem, it represents the average number of servers in a second to respond to requests, and the TPS value and program concurrency is very closely related. Before discussing the Java memory model and threading, let's briefly describe the efficiency and consistency of the hardware.

2. The efficiency and consistency of the hardware

Since there are several orders of magnitude difference between the computer's storage device and the processor's computing power, modern computer systems have to add a cache of read-write speeds as close as possible to the processor's operating speed as a buffer between memory and processor: the data to be used for the operation is copied to the cache. So that the operation can be done quickly, when the operation is finished and then synchronized back to the memory from the cache without waiting for slow memory to read and write.
Cache-based storage interaction solves the contradiction between processor and memory speed, but introduces a new problem: Cache consistency (Coherence). In multiprocessor systems, each processor has its own cache, and they share the same main memory, as shown in: Multiple processor operations involve the same piece of main memory, requiring a protocol that guarantees data consistency, such as MSI, MESI, Mosi, and Dragon protocol. The memory access operations defined in the Java Virtual Machine memory model are comparable to the cache access operations of the hardware, and the Java memory model is described later.

In addition, in order to allow the processor inside the operating unit can be fully utilized, the processor may be in the input code execution (Out-of-order execution) optimization, the processor will be after the calculation of the disorderly execution of the code to reorganize the results to ensure the accuracy of the results. Similar to the processor's unordered execution optimizations, the Java Virtual machine's instant compiler also has similar command reordering (instruction Recorder) optimizations.

3.Java memory model

Defining the Java memory model is not an easy task, and the model must be sufficiently rigorous to allow Java concurrency to be ambiguous, but it must be loose enough to allow the virtual machine's implementation to have enough free space to take advantage of the various features of the hardware (registers, cache, etc.) for better execution speed. After a long period of validation and patching, the Java memory model has matured and perfected after JDK1.5 was released.

3.1 Main memory vs. working memory

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 into memory and removing variables from memory in the virtual machine. The variables here are different from those described in Java programming, which include instance fields, static fields, and elements that make up an array object, but do not include local variables and method parameters, which are thread-private and not shared.

The Java memory model specifies that all variables are stored in main memory, each thread has its own working memory (which can be compared to the cache of the processor before it), that the thread's working memory holds the variable used by the thread to the master memory copy, and all the operations of the thread on the variable (read, Assignment) must be done in working memory and not directly read and write to the variables in main memory. Variables in the other's working memory cannot be accessed directly between different threads, and the transfer of variable values between threads needs to be done in main memory, with threads, main memory, and working memory interacting as shown, and very similar.

The main memory, the working memory, and the Java heap, stack, and method area of the Java memory area are not the same level of memory partitioning.

3.2 Inter-memory interoperability

The Java memory model defines the following eight actions for the specific interaction protocol between main memory and working memory, that is, how a variable is copied from main memory to working memory, how to synchronize from working memory to main memory.

    • Lock: A variable that acts on the main memory and identifies a variable as a thread-exclusive state.
    • Unlock (Unlocked): Acts on the main memory variable, releasing a variable that is in a locked state, and the released variable can be locked by another thread.
    • READ: Acts on the main memory variable, transferring a variable value from main memory to the working memory of the thread for subsequent load actions to use
    • Load: A variable that acts on working memory, which places the value of a read operation from the main memory into a variable copy of the working memory.
    • Use: A variable that acts on the working memory, passing a variable value in the working memory to the execution engine, which is performed whenever the virtual opportunity is to a bytecode instruction that needs to use the value of the variable.
    • Assign (Assignment): A variable acting on a working memory that assigns a value to a working memory from the execution engine, and performs this operation whenever the virtual opportunity is assigned to a byte-code instruction that assigns a value to a variable.
    • Store: A variable acting on a working memory that transfers the value of a variable in the working memory to the main memory for subsequent write operations.
    • Write: A variable that acts on the main memory, which transfers the store operation from the value of a variable in the working memory to a variable in the main memory.

If you want to copy a variable from main memory to working memory, you will need to follow the read and load operations, and if you synchronize the variables from the working memory back to main memory, you should perform the store and write operations sequentially. The Java memory model only requires that the above operations must be executed sequentially, without guarantee that continuous execution is required. That is, between read and load, the store and write can be inserted between the other instructions, such as the main memory of the variable A, b access, the possible order is read A,read B,load b, load a. The Java memory model also stipulates that when performing the eight basic operations above, the following rules must be met:

    • Does not allow one of the read and load, store, and write operations to appear separately
    • A thread is not allowed to discard its most recent assign operation, that is, the variable must be synchronized to main memory after it has changed in working memory.
    • A thread is not allowed to synchronize data from the working memory back to main memory for no reason (no assign operation has occurred).
    • A new variable can only be born in main memory, and it is not allowed to use a variable that is not initialized (load or assign) directly in working memory. That is, the assign and load operations must be performed before a variable is implemented with the use and store operations.
    • A variable allows only one thread to lock it at the same time, and lock and unlock must appear in pairs
    • If you perform a lock operation on a variable, the value of the variable in the working memory will be emptied, and the value of the variable will need to be re-executed before the execution engine uses the variable, either the load or the assign operation.
    • If a variable is not locked by the lock operation beforehand, it is not allowed to perform a unlock operation on it, nor is it allowed to unlock a variable that is locked by another thread.
    • Before performing a unlock operation on a variable, you must first synchronize this variable into main memory (perform store and write operations).
3.3 Re-ordering

In order to improve performance when executing programs, the compiler and processor often reorder instructions. Reordering is divided into three categories:

    1. The re-ordering of compiler optimizations. The compiler can rearrange the execution order of the statements without changing the semantics of the single-threaded procedure.
    2. Reordering of instruction-level parallelism. Modern processors use instruction-level parallelism 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 executing in a disorderly order.

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

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. The Java memory model divides the memory barrier into Loadload, Loadstore, Storeload, and Storestore four types:

3.4 Synchronization mechanism

Describes volatile, synchronized, and final

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

Volatile

Variable defined by volatile, which is specific to:

A thread's write to the volatile variable is bound to be visible to the thread that reads the variable later.

Equivalent to

A thread's reading of a volatile variable must be able to see the last thread before it writes to the variable.

To implement these semantics, Java stipulates that (1) when a thread is going to use a volatile variable in shared memory, the variable A in it will read directly from the main memory without using its own local in-memory copy. (2) When a thread writes to a volatile variable, it flushes the value of the shared variable into shared memory.

As we can see, the volatile variable guarantees that a thread's write to it is immediately flushed to the main memory, and the copy of the other thread is not valid, and it does not guarantee that the operation of the volatile variable is atomic.

Because

public void add(){     a++;         #1 } 

Equivalent to

public void add() {    temp = a;            temp = temp +1;      a = temp;          } 

Code 1 is not an atomic operation, so an operation similar to a++ can cause concurrency data problems.

The write of the volatile variable can be seen by other threads later on, so we can use it to communicate between threads. Such as

volatile int a;public void set(int b) { a = b; }public void get() { int i = a; }

After thread a executes set (), thread B executes get (), which is equivalent to thread a sending a message to thread B.

Synchronized

What if we have to use a++ as a composite operation for inter-thread communication? Java provides us with a synchronized.

public synchronized void add() {    a++;  }

Synchronized makes

The code within its scope is mutually exclusive to different threads, and the thread refreshes the value of the shared variable into shared memory when the lock is released.

We can take advantage of this mutex to communicate between threads. Look at the code below,

public synchronized void add() {    a++; }public synchronized void get() { int i = a; }

When thread a executes add (), thread B calls get (), and because of the mutex, thread B can start executing get () after executing add (), and when thread a executes add (), the value of a is flushed to shared memory when the lock is released. So the value of a that thread B gets is after thread A is updated.

Volatile and synchronized comparisons

Based on the above analysis, we can find that volatile and synchronized are somewhat similar.

    1. When a thread writes to a volatile variable, Java flushes the value into shared memory, and for synchronized, the value of the shared variable is flushed to main memory when the thread releases the lock.

    2. When a thread reads a volatile variable, it invalidates the shared variable in local memory, and for synchronized, when the thread acquires the lock, the shared variable in the current thread's local memory is invalidated.

    3. Synchronized expands the range of visible effects and expands to the block of code that synchronized function.

Final variable

The final keyword can be used for variables, methods, and classes, and we'll look at the final variable here.

The special thing about the final variable is that

Once the final variable is initialized, it cannot change its value.

The value here refers to an object or array referring to the object or array's reference address. Therefore, after a thread has defined a final variable, any other thread will get the variable. But one thing to note is that when this final variable is an object or an array,

    1. Although we cannot say that this variable is assigned to another object or array, we can change the object's field or the elements in the array.

    2. The thread's changes to the domain of the object variable or to the elements of the data do not have thread visibility.

Summarize

Sometimes, we are learning a technology process, and can not only be limited to how to use, know how to use, we should explore in depth, why so that we can get the results we want? It is necessary to know the reason.

。。。。。。。。。。。。。。。。。。。。。。。。。。。

3.5 atomicity, visibility and ordering

Introducing three properties

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

The Java memory model is built around the three characteristics of how the concurrency process handles atomicity, visibility, and ordering, and the following are the implementation principles of these three features:

1. atomicity (atomicity)

Atomic variable operations, directly guaranteed by the Java memory model, include read, load, use, assign, store, and write six, which can roughly be considered atomic for access and read-write of the underlying data type. If the scenario requires a wider range of atomicity guarantees, the Java memory model also provides lock and unlock operations to meet this requirement, although the virtual machine does not open lock and unlock operations directly to the user, But it provides a higher level bytecode directive Monitorenter and monitorexit to use these two operations in stealth, the two bytecode instructions reflected in the Java code is the synchronization block---synchronized keyword, Thus the operation between the synchronized blocks is also atomic.

2. Visibility (Visibility)

Visibility means that when a thread modifies the value of a thread-shared variable, other threads can immediately know the change. The Java memory model realizes visibility by synchronizing the new values back to main memory after the variable is modified, and by refreshing the variable values from the main memory before the variable is read, which relies on the main memory as a means of transmitting the media, whether it is a normal variable or a volatile variable. The difference between a normal variable and a volatile variable is that the special rule of volatile guarantees that the new value can be immediately synchronized to the main memory and flushed from memory immediately before each use. Because we can say that volatile guarantees the visibility of variables when threading, while normal variables do not guarantee this.

In addition to the volatile, Java has two keywords to achieve visibility, they are synchronized. The visibility of a synchronization block is obtained by synchronizing this variable back into main memory (performing store and write operations) before performing a unlock operation on a variable, while the final keyword's visibility means that the final-modified field is the constructor once initialized And the constructor does not pass the "this" reference, the value of the final field can be seen in other threads.

3. Order (ordering)

The natural ordering of the programs in the Java memory model can be summed up in one sentence: If you observe in this thread, all operations are orderly, and if you observe another thread in one thread, all operations are unordered. The first half of the sentence refers to "line range performance as serial semantics", the latter sentence refers to the "order reordering" phenomenon and "working memory Master Memory Synchronization delay" phenomenon.

The Java language provides the volatile and synchronized two keywords to ensure the ordering of operations between threads, and the volatile keyword itself contains the semantics of prohibiting command reordering, while synchronized is the " A variable that allows only one thread to lock on it at the same time "this rule determines that two synchronized blocks holding the same lock can only be entered serially.

The principle of antecedent occurrence:

If all the ordering in the Java memory model is done only by volatile and synchronized, then some of the operations will become verbose, but we do not feel this when writing Java concurrency code because there is an "antecedent" in the Java language ( Happen-before) principle. This principle is very important, it is the main reliance to judge whether the data is competitive and whether the thread is safe or not.

The first occurrence principle refers to the sequential relationship between two operations defined in the Java memory model, and if operation a precedes operation B, it is said that the effect of operation A can be observed by Operation B before Operation B, "Impact" includes modifying the values of shared variables in memory, sending messages, calling methods, etc. What does it mean? The following example:

1; //thread B executes j = i; 2; 

Assuming that the operation "I=1" in thread a precedes the operation "j=i" of thread B, then we can determine that the value of the variable J must be equal to 1 after the operation of Line B, and that the basis for this conclusion is two, and that the results of the "i=1" can be observed in accordance with the principle of antecedent occurrence. The second is that no other thread will modify the value of the variable i until thread C has finished. Now consider thread C, we still maintain the antecedent relationship between threads A and B, and thread C appears between the thread A and B operations, but C and B do not have a prior relationship, then the value of J may be 1, or 2, because the impact of thread C corresponding to the variable I may be observed by thread B, or may not be observed, At this point, thread B has the risk of reading to outdated data, without the security of multithreading.

The following are some of the "natural" antecedent relationships in the Java memory model, which are already present 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.

A. Order of Procedure rules (pragram Order rule): In a thread, according to the sequence of program code, the preceding operation precedes the operation that is written in the back. It should be accurate to control the flow order rather than the program code order, because the branching and looping structures are considered.

B. Tube lock rule: A unlock operation first occurs after the lock operation facing the same lock. The same lock must be emphasized here, and the "back" refers to the chronological order of the time.

C.volatile variable rules (volatile Variable rule): Write to a volatile variable precedes the read operation of the variable, where the "back" also refers to the chronological order.

D. Thread start rule: the Start () method of the thread object takes precedence over each action of this thread.

E. Thread finally rule (thread termination rule): All operations in the thread occur first in the termination detection of this thread, we can end with the Thread.Join () method, Thread.isalive () The return value of such a segment detects that the thread has terminated execution.

F. Thread interruption rule: the invocation of the thread interrupt () method first occurs when the code of the interrupted thread detects that the interrupt event occurred, and can be passed thread.interrupted () method to detect if an interrupt occurred.

G. Object termination rule (Finalizer rule): An object initialization completes (the construction method execution completes) first occurs at the beginning of its finalize () method.

G. transitivity (transitivity): If operation a first occurs in operation B, Operation B first occurs in Operation C, it can be concluded 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. Therefore, the order of time and the principle of the occurrence of a basic relationship between, so the measurement of concurrency security must be based on the principle of first occurrence.

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

Java Memory Model (reprint)

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.