Java memory model explained

1. JAVA's concurrency model

Shared memory model

       In the concurrency model of shared memory, threads share the public state of the program , and threads communicate implicitly by reading and writing the public state in memory.

       The memory refers to the main memory , which is actually a small part of the physical memory

2. Abstraction of JAVA memory model

2.1 Which data in java memory is thread-safe and which is unsafe

  1. Non-thread safety: All instance domains, static domains, and array elements in java are stored in heap memory , and these data are shared by threads , so there will be memory visibility problems
  2. Thread safety: Local variables, method definition parameters, and exception handler parameters are data in the virtual machine stack of the current thread , and thread sharing will not be performed , so there will be no memory visibility problems

2.2 The nature of communication between threads

  1. The essence of inter-thread communication is: JMM is the JAVA memory model for control. JMM determines when a thread's writing of shared variables is visible to other threads.

image

As can be seen from the above figure, the communication between threads is to pass messages through the main memory. When each thread is performing shared data processing, the shared data is copied to the current thread local (each thread has its own Memory) to operate.

  1. Message communication process (without considering data security issues):
  • Thread 1 loads the shared variable A in the main memory into its own local memory for processing. For example, A = 1;
  • At this time, the modified shared variable A is flashed into the main memory, and then the thread 2 reads the shared variable A in the main memory to the local memory for operation;

The entire data interaction process is controlled by JMM, which mainly controls how the main memory and the local memory of each thread interact to provide visibility of shared data

3. Reorder

In order to improve efficiency when the program is executed, the program instructions will be reordered

3.1 Reordering categories

  • Compiler optimized reordering

The compiler optimizes the order of statement execution without changing the semantics of a single-threaded program

  • Instruction set parallel reordering

If there is no data dependency, the processor can change the execution order of the statement corresponding to the machine instruction

  • Memory system reordering

Since the processor uses cache and read/write buffers, this makes load and store operations appear to be performed out of order

3.2 Reordering process

image

The above three kinds of reordering will cause memory visibility problems when we write concurrent programs.

JMM's compiler reordering rules prohibit specific types of compiler reordering;

JMM's processor reordering rules require the java compiler to insert specific memory barrier instructions when generating instruction sequences , and use memory barrier instructions to prohibit specific types of processors from reordering

3.3 Processor reordering

In order to avoid the delay of the processor waiting to write data to the memory, a buffer is added between the processor and the memory , so that the processor can always write data to the buffer, and wait until a certain time to read the data in the buffer once. Sexually brushed into the memory.

Advantages:

  1. The processor has different pauses, which improves the efficiency of the processor
  2. Reduce the memory bus usage when writing data to the memory

Disadvantages:

  1. The write buffer on each processor is only visible to the current processor, so the execution order of memory operations will not match the actual situation.

For example, the following scenario:

image

       In the current scenario, it may appear that processor A and processor B did not flush the data in their respective write buffers back to the memory, and assign the values ​​A = 0 and B = 0 read from the memory to X and Y , At this time, the data in the buffer is flushed into the memory, causing the final result to be inconsistent with the actual desired result. Because only the data in the buffer is flushed into the memory is the real execution

       The specific interaction protocol between the above main memory and the working memory , that is, how a variable is copied from the main memory to the working memory , and how to synchronize from the working memory to the main memory is the implementation details. JMM defines the following 8 operations to complete

operating Semantic analysis
lock Act on variables in main memory, mark a variable as a thread exclusive state
unlock Act on the variable of the main memory, release a variable that is in a locked state, and the released variable can be locked by other threads
read Acting on the variable of the main memory, transfer a variable value from the main memory to the working memory of the thread for the subsequent load action
load Acting on the variable of the working memory, it puts the variable value obtained from the main memory by the read operation into the variable copy of the working memory
use Act on the variable of the working memory, pass a variable value in the working memory to the execution engine
assign (assignment) A variable acting on the working memory, which assigns a value received from the execution engine to a variable in the working memory
store A variable acting on the working memory, transfer the value of a variable in the working memory to the main memory
for subsequent write operations
write Acting on the variable of the working memory, it transfers the value of the store operation from a variable in the working memory to the variable in
the main memory

       If you want to copy a variable from the main memory to the working memory, you need to perform read and load operations in sequence. If you want to synchronize the variable from the working memory to the main memory, you need to perform store and write operations in sequence. However, the Java memory model only requires that the above operations must be performed sequentially, and there is no guarantee that they must be performed continuously

Operation execution flow chart:

image

Synchronization rule analysis

  1. A thread is not allowed to synchronize data from the working memory back to the main memory for no reason (no assign operation has occurred)
  2. 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. That is, before implementing use and store operations on a variable, you must first assign and load operations yourself.
  3. A variable can only be locked by one thread at the same time, but the lock operation can be repeated multiple times by the same thread. After the lock is executed multiple times, the variable will be unlocked only if the unlock operation is executed the same number of times. Lock and unlock must appear in pairs.
  4. If you perform a lock operation on a variable, the value of this variable in the working memory will be cleared. Before the execution engine uses this variable, you need to re-execute the load or assign operation to initialize the value of the variable.
  5. If a variable is not locked by a lock operation in advance, it is not allowed to perform an unlock operation; it is also not allowed to unlock a variable that is locked by other threads.
  6. Before performing an unlock operation on a variable, the variable must be synchronized to the main memory (execute store and write operations)

3.4 Memory barrier instructions

       In order to solve the memory errors caused by processor reordering, the java compiler inserts memory barrier instructions at the appropriate position of the generated instruction sequence to prohibit specific types of processor reordering

Memory barrier instructions

Barrier type Command example Description
LoadLoadBarriers Load1;LoadLoad;Load2 Load1 data load occurs before Load2 and all subsequent data loads
StoreStoreBarriers Store1;StoreStore;Store2 Store1 data flushing back to main storage occurs before Store2 and all subsequent data flushing back to main storage
LoadStoreBarriers Load1;LoadStore;Store2 Load1 data loading occurs before Store2 and all subsequent data are flushed back to main memory
StoreLoadBarriers Store1; StoreLoad; Load2 Store1 data flushing back to memory occurs before Load2 and all subsequent data loads

3.5 happens-before (preceding rule)

       The happens-before principle helps to ensure the atomicity, visibility and order of program execution. It is the basis for judging whether there is competition in the data and whether the thread is safe

       In JMM, if the result of one operation needs to be visible to another operation , then the two operations must have a happens-before relationship (the two operations can be the same thread or not the same thread)

Rule content :

  • Program sequence rules: Refers to the control of the code sequence in a thread, such as branching, looping, etc., that is, semantic serialization must be guaranteed in a thread, that is to say, execution in code order
  • Locking rules: An unlock operation must occur before a lock operation, that is to say, if a lock is unlocked and then locked, then the locking action must be after the unlocking action (the same A lock)
  • Volatile variable rules: The write operation of a volatile variable must occur before the read operation of this variable. This ensures the visibility of the volatile variable. The simple understanding is that the volatile variable is forced from the thread every time it is accessed by the thread. Read the value of the variable in the main memory, and when the variable changes, it will force the latest value to be flushed to the main memory. At any time, different threads can always see the latest value of the variable
  • Thread start rule: The thread start method start() must occur before all operations of the current thread
  • Thread termination rule: All operations in a thread must occur before the thread terminates. The function of the Thread.join() method is to wait for the currently executing thread to terminate. Suppose that the shared variable is modified before thread B terminates. After thread A successfully returns from the join method of thread B, the modification of shared variable by thread B will be visible to thread A
  • Thread interruption rules: The thread calling the interrupt() method must occur before the interrupted thread's code checks out the interrupt event
  • Object termination rule: The initialization of the object is completed before the object is recycled
  • Transitivity rule: If operation A occurs before operation B, and operation B occurs before operation C, then operation A must occur before operation C

Note : The happens-before relationship between two operations does not mean that the previous operation must be executed before the next operation. It only needs the result of the previous operation to be visible to the next operation, and the previous operation must be arranged in order. Before the next operation.

3.6 Data dependence

       Result is a result of operation of one operation before an impact, this time in the process of the compiler and processor does not change the current data dependency exists when the operation data dependency order of execution of two operations

Note : The data dependency mentioned at this time is only for the sequence of instructions executed in a single processor or operations executed in a single thread . Compiler and processor will not consider the situation of different processors and different threads

3.7 as-if-serial

       In the case of a single thread , the execution result of the reordering program cannot be changed no matter how, so if it is in a single processor or a single thread, the compiler and the processor will not reorder the data-dependent operations. On the contrary, if there is no data-dependent operation, instruction rearrangement may occur.

5. Data competition and sequential consistency

Data competition will only occur in multi-threaded situations

5.1 Data competition

       Write a variable in one thread, read a variable in another thread, and write and read are not synchronized

5.2 Sequential consistency

       If the program can correctly use the synchronization mechanism under multi-threaded conditions, then the execution of the program will have sequential consistency (just like executing under single-threaded conditions), and the final result of the program will be the same as your expected result

5.3 Sequentially consistent memory model

5.3.1 Features :

  • All operations in a thread must be executed in the order of the program
  • All operations must be atomic and visible to other threads

5.3.2 Concept:

       Conceptually, sequential consistency has a single global memory, and at most one thread can be connected to the memory at any point in time. When in a multi-threaded scenario, all memory read and write operations will become serialized.

5.3.3 Case:

       For example, there are multiple concurrent threads ABC, thread A has two operations A1 and A2, and their execution order is A1 -> A2. The B thread has three operations B1 B2 B3, and their execution order is B1 -> B2 -> B3. C thread has two operations C1 C2, then the order of their execution in the program is C1 -> C2.

Scene analysis:

Scenario 1: Concurrent safe (synchronous) execution sequence

A1 -> A2 -> B1 -> B2 ->B3 -> C1 -> C2

Scenario 2: Concurrent unsafe (non-synchronized) execution sequence

A1 -> B1 -> A2 -> C1 -> B2 ->B3 -> C2

in conclusion:

       In an asynchronous scenario, even if each operation in the three threads is executed out of order, the respective operations in each thread remain in order. And all threads can only see a consistent overall execution order, which means that all three threads see this order: A1 -> B1 -> A2 -> C1 -> B2 -> B3 -> C2, because Every operation in the sequential consistent memory model must be immediately visible to any thread.

       The above case scenario is not like this in JMM. Not only the overall execution order of unsynchronized programs in JMM has changed, but the execution order of operations seen by each thread is also different .

       For example, as mentioned above, if thread A writes the value a = 2 of the variable into its own local memory, but has not been flashed into the main memory, the value has changed from the point of view of thread A, but other threads B and C are basically If you can't see the value change, it is considered that the operation of thread A has not happened yet, and only thread A can flush the value in the working memory back to main memory, thread B and thread C. However, if it is synchronized, the execution results of the sequential consistency model and the JMM model are the same, but the execution order of the program is not necessarily, because in JMM, there will be a phenomenon of instruction rearrangement, so the execution order will be inconsistent .

Guess you like

Origin blog.csdn.net/weixin_38071259/article/details/112277114