In-depth understanding of jvm-java memory model (combined with volatile)

1. Structure

Insert picture description here

  • All variables are stored in main memory
  • Each thread has its own working memory
  • The working memory of a thread holds a copy of the main memory of the variables used by the thread
  • All operations on variables by threads must be performed in working memory, and must not directly manipulate main memory
  • The transfer of variables between threads must be done interactively by main memory

Insert picture description here

2. Interactive atomic operations between memory

Lock (lock): a variable that acts on main memory, it identifies a variable as a thread exclusive state.

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

· Read (read): a variable that acts on the main memory, it transfers the value of a variable from the main memory to the working memory of the thread for subsequent load actions.

· Load (load): acts on the variables of the working memory, it puts the value of the variable obtained from the main memory by the read operation into the variable copy of the working memory.

Use (use): a variable that acts on the working memory, it passes the value of a variable in the working memory to the execution engine, and the virtual machine will perform this operation whenever it encounters a bytecode instruction that requires the value of the variable.

Assign (assignment): A variable that acts on working memory. It assigns a value received from the execution engine to a variable of working memory. This operation is performed whenever the virtual machine encounters a bytecode instruction that assigns a value to the variable.

· Store (storage): A variable that acts on the working memory, it transfers the value of a variable in the working memory to the main memory for use in subsequent write operations.

· Write (write): acts on the variables of the main memory, it puts the values ​​of the variables obtained from the working memory by the store operation into the variables of the main memory.

If you want to copy a variable from main memory to working memory, you must perform read and load operations in order. If you want to synchronize variables from working memory back to main memory, you must perform store and write operations in order. Note that the Java memory model only requires that the above two operations must be performed in sequence , but does not require continuous execution.

3. volatile understanding

  • Ensure the visibility of this variable to all threads (once a thread changes the value, the new value can be immediately known to other threads) (thread safety is not guaranteed, for example: ABA 2 1 3, etc.)
  • Prohibit instruction reordering

Variables modified by volatile have the following properties:

❑ Visibility. When reading a volatile variable, you can always see (any thread) the last write to the volatile variable.

❑ Atomicity: The reading / writing of any single volatile variable is atomic , but the compound operation similar to volatile ++ is not atomic. (E.g. long, double)

Reordering (also understand read after write, write after read, write after write)

What is reordering: In order to improve performance, compilers and processors often reorder instructions in a given code execution order.
Reason: A good memory model will actually relax the constraints on the processor and compiler rules, which means that both software technology and hardware technology struggle for the same goal
: without changing the results of program execution, as much as possible Improve execution efficiency. JMM minimizes constraints on the bottom layer so that it can exert its own
advantages. Therefore, in order to improve performance when executing programs, compilers and processors often reorder instructions. General reordering can be divided into the following three
types:
1. Compiler optimized reordering. The compiler can rearrange the execution order of statements without changing the semantics of the single-threaded program;
2. Instruction-level parallel reordering. Modern processors use instruction-level parallel technology to overlap multiple instructions. If there is no data dependency, the processor
can change the execution order of the machine instructions corresponding to the statement;
3. Reordering of the memory system. Because the processor uses cache and read / write buffers, this makes load and store operations appear to be executed out of order.
Insert picture description here

Insert picture description here
If you do not use the volatile modification when defining the initialized variable, the last code "initialized = true" in thread A may be executed in advance due to the optimization of instruction reordering (although Java is used as pseudo code, but the heavy Sorting optimization is a machine-level optimization operation, and early execution means that the assembly code corresponding to this statement is executed in advance), so that the code that uses configuration information in thread B may have errors, and the volatile keyword can avoid such situations. happened

There is another example:
Insert picture description here

There may be i = 0, j = 0

Reason:
here thread one and thread two can write shared variables to their own write buffer (A1, B1) at the same time, then read another shared variable (A2, B2) from memory, and finally write themselves to the buffer The saved dirty data is refreshed into the memory (A3, B3). When executed in this timing sequence, the program can get the result of x = y = 0.
Insert picture description here


The performance consumption of volatile variable read operations is almost the same as that of ordinary variables, but write operations may be slower because it requires inserting many memory barrier instructions in the local code to ensure that the processor does not execute out of order.

4. For 64-bit basic types long and double

If multiple threads share a variable of type long or double that is not declared volatile, and read and modify them at the same time, then some threads may read a variable that is neither the original value nor other threads The modified value represents the "half variable" value. But this kind of reading of "half variable" is very rare

5. Happens-Before

It is a very useful method to judge whether data is competing and whether the thread is safe.

An operation "occurring first in time" does not mean that the operation will be "occurring first". So if an operation "occurs first", can it be deduced that this operation must be "time first"? Unfortunately, this inference is also untenable.

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 that the previous operation (the result of the execution) is visible to the latter operation , and the previous operation is ranked before the second operation (the f irst is visible to and ordered before the second).

There is basically no causal relationship between the time sequence and the principle of prior occurrence, so when we measure concurrent security problems, we should not be disturbed by the time sequence. Everything must be based on the principle of prior occurrence.

i = 1;       //线程A执行
j = i ;      //线程B执行

Is j equal to 1? Assume that the operation of thread A (i = 1) happens-before the operation of thread B (j = i).
Then it can be determined that j = 1 must be established after the execution of thread B.
If they do not have the happens-before principle, then j = 1 may not be true.

(Even if the code executes j = 1 first, then executes j = i, it is not necessarily j = 1, mainly depends on whether it meets happens-before)


Here are some "natural" pre-emptive relations under the Java memory model

Program Order Rule: In a thread, according to the control flow order, the operation written in the front occurs before the operation written in the back. Note that the order of control flow is not the order of program code, because the structure of branches and loops should be considered.

· Monitor Lock Rule: An unlock operation occurs before the lock operation of the same lock . What must be emphasized here is "the same lock", and "behind" refers to the chronological order.

· Volatile Variable Rule: A write operation on a volatile variable occurs first in a subsequent read operation on this variable . Here "back" also refers to the chronological order.

Thread start rule (Thread Start Rule): The start () method of the Thread object first occurs in every action of this thread.

· Thread termination rule (Thread Termination Rule): All operations in the thread first occur in the termination detection of this thread, we can pass the Thread :: join () method to end, Thread :: isAlive () return value and other means Check whether the thread has terminated execution.

Thread Interruption Rule (Thread Interruption Rule): The call to the thread interrupt () method occurs before the code of the interrupted thread detects the occurrence of an interrupt event. You can detect whether an interrupt occurs through the Thread :: interrupted () method.

Object finalization rule (Finalizer Rule): The initialization of an object (the completion of the execution of the constructor) first occurs at the beginning of its finalize () method.

· Transitivity: If operation A occurs before operation B and operation B occurs before operation C, then it can be concluded

Based on volatile happens-before

Insert picture description here
1) According to the rules of program sequence, 1 happens-before 2; 3 happens-before 4.

2) According to the volatile rule, 2 happens-before 3.

3) According to the transitive rule of happens-before, 1 happens-before 4.

6.java thread state switching

Insert picture description here
· Blocked: The thread is blocked. The difference between "blocked state" and "waiting state" is that "blocking state" is waiting for an exclusive lock to be acquired. This event will occur when another thread gives up the lock. ; "Waiting state" is waiting for a period of time, or wake-up action occurs. When the program is waiting to enter the synchronization area, the thread will enter this state.
Insert picture description here

7. Memory semantics based on volatile

  • Writing semantics: When writing a volatile variable, JMM will refresh the shared variable value in the local memory corresponding to the thread to the main memory.
  • Read semantics: When reading a volatile variable, JMM will invalidate the local memory corresponding to the thread. The thread will then read the shared variable from main memory.

Implementation of volatile memory semantics (implementation of prohibiting instruction reordering)

❑ Insert a StoreStore barrier in front of each volatile write operation.
❑ Insert a StoreLoad barrier after each volatile write operation.
❑ Insert a LoadLoad barrier after each volatile read operation.
❑ Insert a LoadStore barrier after each volatile read operation.

Insert picture description here
example:
Insert picture description here
Insert picture description here

Published 37 original articles · praised 6 · visits 4637

Guess you like

Origin blog.csdn.net/littlewhitevg/article/details/105568194