Java memory model and threads

TPS: To measure the performance of a service, the number of transactions per second (Transactions Per Second, TPS) is one of the most important indicators, representing the total number of requests that the server can respond to within one second.

The vast majority of computing tasks cannot be completed by the processor alone. The processor must at least interact with the memory, such as reading data and storing operation results. This I/O operation is difficult to eliminate, and computer storage devices There is a gap of several orders of magnitude with the computing speed of the processor, so a layer of cache with a read and write speed as close as possible to the computing speed of the processor should be added as a buffer between the memory and the processor, so that the processor does not have to wait for the slowness The memory is read and written.

Although the introduction of Cache has solved the contradiction between the speed of the processor and the memory, it has also brought a new problem: Cache Coherence. In a multiprocessor system, each processor has its own cache, and they share the same Main Memory, as shown in the figure below. When the computing tasks of multiple processors involve the same main memory area, the respective cached data may be inconsistent, so whose cached data shall prevail when synchronizing to the main memory? In order to solve the problem of consistency, each processor needs to follow some protocols when accessing the cache, such as MSI, MESI, MOSI and so on.

The so-called "memory model" refers to the process abstraction of read and write access to a specific memory or cache under a specific operation protocol. Different physical machines have different memory models, and the Java virtual machine also has its own memory model.

In addition to increasing the cache, in order to make full use of the arithmetic unit inside the processor, the processor may perform out-of-order execution optimization on the input code, and then reorganize the out-of-order execution result after the calculation. The Java virtual machine's real-time compiler also has Similar instruction reordering optimization.

12.3 The Java Memory Model

The Java virtual machine attempts to define a Java Memory Model (JMM) to shield the memory access differences between hardware and operating systems, so that Java programs can achieve consistent memory access effects on various platforms.

1. Main memory and working memory

The main goal of the Java memory model is to define the access rules for each variable in the program, that is, the low-level details such as storing or retrieving variables into or out of memory in the virtual machine. Variables here are all different from variables in Java programming. They include instance fields, static fields, and elements that constitute array objects, but do not include local variables and method parameters, because the latter are private to threads and do not Will be shared, there is no competition problem (PS: If the local variable is a reference type, the object it refers to can be shared by each thread in the Java heap, but the reference itself is in the local variable table of the Java stack, which is thread-private ). The Java memory model does not restrict specific registers or caches to interact with main memory, nor does it restrict the just-in-time compiler from making optimizations such as adjusting the order of code execution.

The Java memory model stipulates that all variables are stored in the main memory (Main Memory), each thread also has its own working memory (Working Memory), and all operations (reading, assignment, etc.) of the thread to variables must be working In memory, instead of directly reading and writing variables in main memory. Different threads cannot directly access variables in each other's working memory, and the transfer of variable values ​​between threads needs to be done through the main memory. The interaction diagram of threads, main memory, and working memory is as follows, which can be compared with the relationship between the processor, cache, and main memory in the above figure:

The main memory and working memory mentioned here are not the same level of memory division as the Java heap, stack, method area, etc. in the Java memory area mentioned earlier, and the two have nothing to do with each other. But if you have to correspond, the main memory corresponds to the object instance data part in the Java heap, and the working memory corresponds to part of the data in the virtual machine stack. At a lower level, main memory corresponds to physical hardware memory, and working memory may correspond to registers and caches, because the main access to read and write when a program is running is working memory.

2. Interaction between memory

The interaction between the main memory and the working memory, that is, the variables are copied from the main memory to the working memory, synchronized from the working memory back to the main memory, etc. The Java memory model defines the following 8 operations to complete, and the virtual machine implementation must ensure the following. Every operation is atomic and indivisible (except for double and long types):

(1) lock (lock): a variable acting on the main memory to identify a variable as the exclusive state of a thread.

(2) unclock (unlock): a variable that acts on the main memory, releases a variable in a locked state, and the released variable can be locked by other threads.

(3) read (read): a variable acting on the main memory, transferring the value of a variable from the main memory to the working memory of the thread for use by the subsequent load action.

(4) load (load): acting on the variable of the working memory, put the variable value obtained from the main memory by the read operation into the variable copy of the working memory.

(5) use: a variable acting on the working memory, passing the value of a variable in the working memory to the execution engine.

(6) assign (assignment): a variable that acts on the working memory, and assigns the value received by the execution engine to the variable of the working memory.

(7) store (storage): a variable acting on the working memory, transferring the value of a variable in the working memory to the main memory for subsequent write operations.

(8) write (write): acting on a variable in the main memory, putting the value of the variable obtained from the working memory by the store operation into the variable in the main memory.

If a variable is to be copied from main memory to working memory, read and load operations are performed sequentially;

To synchronize a variable from working memory back to main memory, store and write operations are performed sequentially.

 

Provisions for the above 8 operations:

(1) One of the read and load, store and write operations is not allowed to appear alone, that is, a variable is not allowed to be read from the main memory but the working memory does not accept it, or the working memory initiates a write-back but the main memory does not accept it appear.

(2) A thread is not allowed to discard its most recent assign operation, that is, after a variable has changed in working memory, the change must be synchronized to main memory.

(3) A thread is not allowed to synchronize data from the thread's working memory to the main memory for no reason (without any assign operation).

(4) A new variable can only be born in the main memory. It is not allowed to directly use an uninitialized (load or assign) variable in the working memory. In other words, before using and storing a variable, it must be The assign and load operations were performed first.

(5) A variable can only be locked by one thread at the same time, but the lock operation can be repeated by the same thread for many times. After the lock is executed multiple times, the variable will only be unlocked by performing the same number of unclock operations. .

(6) If the lock operation is performed on a variable, the value of the secondary variable in the working memory will be cleared. Before the execution engine uses this variable, it is necessary to re-execute the load or assign operation to initialize the value of the variable.

(7) If the lock operation is performed on a variable, the value of the secondary variable in the working memory will be cleared. Before the execution engine uses this variable, the load or assign operation needs to be re-executed to initialize the value of the variable.

(8) Before executing unclock on a variable, the variable must be synchronized back to the main memory (store and write operations are performed).

3. Special rules for volatile variables

When a variable is defined as volatile, it has two properties:

(1) Feature 1: Ensure the visibility of this variable to all threads, which means that when a thread modifies the value of this variable, the new value is immediately known to other threads, and the transfer of ordinary variables between threads needs to be done with the help of main memory ;

PS: However, this does not mean that operations based on volatile variables are safe under concurrency. Since volatile variables can only guarantee visibility, in addition to using the volatile keyword in scenarios that meet the following two conditions, we still need to ensure atomicity by locking (synchronized or the atomic class in java.util.concurrent).

Condition 1: The result of the operation does not depend on the current value of the variable, or ensures that only a single thread modifies the variable value.

Condition 2: The variable does not need to participate in the invariant constraint with other state variables.

(2) Feature 2: Disable instruction reordering optimization. Ordinary variables can only guarantee that the correct results can be obtained in all places that depend on the assignment results during the execution of the method, but cannot guarantee that the order of variable assignment operations is consistent with the execution order in the program code. (Instruction reordering can interfere with concurrent execution of the program)

The special rules defined by the Java memory model for volatile variables:

Assuming that T represents a thread, and V and W represent two volatile variables, respectively, the following rules need to be satisfied when performing read, load, use, assign, store, and write operations:

(1) Thread T can perform the use action on variable V only when the previous action performed by thread T on variable V is load; and, only when the latter action performed by thread T on variable V is use, the thread T can perform the load operation on the variable V. The use operation of the variable V by the thread T can be considered to be associated with the load and read operations of the variable V by the thread T, and must appear together continuously. This rule requires that in the working memory, every time the variable V is used, the latest value must be refreshed from the main memory to ensure that the modified value of the variable V made by other threads can be seen.

(2) Thread T can perform a store operation on variable V only when the previous action performed by thread T on variable V is assign; and only when the latter action performed by thread T on variable V is a store operation, Thread T can perform the assign operation on the variable V. The assign operation of the thread T to the variable V can be considered as being associated with the store and write operations of the thread T to the variable V, and must appear together continuously. This rule requires that in the working memory, every time V is modified, it must be synchronized back to the main memory immediately to ensure that other threads can see their modifications to the variable V.

(3) Assume that operation A is a use or assign action performed by thread T on variable V, assume that operation F is a load or store operation associated with operation A, and assume that operation P is a read or write operation on variable V corresponding to operation F ; type, assume that action B is a use or assign action performed by thread T on variable W, assume that action G is a load or store operation associated with action B, and assume that action Q is a read or write action on variable V corresponding to action G operate. If A precedes B, then P precedes Q. This rule requires that variables modified by valitile will not be optimized by instruction reordering, ensuring that the execution order of the code is the same as the order of the program.

4. Special rules for long and double variables

The Java memory model requires eight operations of lock, unlock, read, load, assign, use, store, and write to be atomic, but for 64-bit long and double, their load, store, read, and write are allowed without guaranteeing atomicity sex. However, it is strongly recommended that the virtual machine implement atomicity for long and double.

In actual development, almost all commercial virtual machines choose to read and write 64-bit data as atomic operations.

5. Atomicity, Visibility, Order

Atomicity:

The atomic variable operations directly guaranteed by the Java memory model include read, load, assign, use, store, and write. It is generally believed that access to basic data types is atomic (exception: long and double types).

Visibility:

Visibility means that when a thread modifies the value of a shared variable, other threads are immediately aware of the modification. The Java memory model achieves visibility by synchronizing the new value back to main memory after the variable is modified, and flushing the variable value from main memory before the variable is read.

In addition to volatile, synchronized and final can also achieve visibility.

(1) Synchronized synchronization block: Before unlocking a variable, the variable must be synchronized back to the main memory (store, write);

(2) final keyword: Once the final-modified field is initialized in the constructor, and the constructor does not pass the "this" reference, the final-modified value can be seen in other threads.

Orderly:

If observed in this thread, all operations are ordered - the thread appears serial;

If one thread observes another, all operations are out of order - instruction reordering, working memory and main memory synchronization delays.

Java provides two keywords volatile and synchronized to ensure the ordering of operations between threads:

Volatile: disable instruction reordering;

Synchronized: A variable only allows one thread to perform a lock operation on it at the same time, that is, two synchronized blocks holding the same lock can only enter serially.

6. Happen first principle

Occurrence before refers to the partial order relationship between two operations defined in the Java memory model. If operation A occurs first before operation B, then before operation B occurs, the effect of operation A can be observed by operation B.

The natural look-ahead relationship that exists in the Java memory model:

(1)  Program order rules : Within the same thread, according to the order in which the codes appear, the preceding code precedes the following code, which is precisely the control flow order, because branch and loop structures must be considered.

(2)  Monitor locking rules : An unlock operation occurs first in a later (time) lock operation on the same lock.

(3)  volatile variable rule : a write operation to a volatile variable occurs first (in time) after a read operation of this variable.

(4)  Thread start rules : Thread's start( ) method occurs first in every operation of this thread.

(5)  Thread termination rule : All operations of a thread are preceded by the termination detection of this thread. The termination of the thread can be detected by means such as the end of the Thread.join( ) method and the return value of Thread.isAlive( ).

(6)  Thread interruption rules : The call to the thread interrupt( ) method occurs first in the code of the interrupted thread and detects the occurrence of the interrupt event. You can use the Thread.interrupt( ) method to detect whether the thread is interrupted.

(7)  Object finalization rules : The initialization of an object is completed before the start of its finalize() method.

(8)  Transitivity : If operation A precedes operation B, and operation B precedes operation C, then operation A precedes operation C.

Summary: An operation "occurs first in time" does not mean that the operation occurs first; an operation that occurs first does not mean that the operation occurs first in time (the occurrence of reordering).

The order of time has little to do with what happens first. Therefore, when measuring concurrency security issues, do not be affected by the order of time. Everything is based on the principle of what happens first.

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326393085&siteId=291194637