Java concurrency Java memory model happens-before principle

1. Two key issues

In concurrent programming, two key issues need to be addressed: how to communicate between threads and how to synchronize between threads.
Communication refers to how information is exchanged between threads. Synchronization refers to how to control the relative order in which operations occur between different threads.

In imperative programming, there are two communication mechanisms between threads: shared memory and message passing.

In the shared memory concurrency model, the common state of the program is shared between threads, and implicit communication is carried out by writing to reading the common state in the memory; and its synchronization is also performed explicitly, and it must be shown that a certain piece of code needs to be specified in the thread. Mutually exclusive execution.
In the concurrency model of message passing, there is no common state between threads, and communication must be displayed by sending messages; but its synchronization is done implicitly, because the sending of the message must be before the receiving of the message.

Java's concurrency uses a shared memory model. Next, let's understand how Java solves these two problems. That is, how the threads of Java communicate and synchronize.

2. Java memory model

Virtual machine runtime data area

From the Java runtime data area, we know that the method area and heap are data areas shared by all threads. The virtual machine stack, local method stack, and program counter are thread-private memory.

The communication between Java threads is controlled by the Java Memory Model (JMM). JMM determines when a thread's write to a shared variable is visible to another thread.
The main goal of JMM is to define the access rules of each variable in the program, that is, the low-level details of storing variables in memory and removing variables from memory in the virtual machine.
JMM defines the abstract relationship between threads and main memory: shared variables between threads are stored in the main memory, each thread has a private local memory, the local memory stores the thread to read/write shared variables Copy.

Local memory is an abstract concept of JMM and does not really exist. It covers caches, write buffers, registers, and other hardware and compiler optimizations. Corresponding to some areas in the virtual machine stack.

Abstract structure diagram of Java memory model
The relationship among threads, main memory, and working memory

How does thread A communicate with thread B:

  1. Thread A flushes the updated shared variables in local memory A to main memory
  2. Thread B goes to main memory to read the shared variables that thread A has updated before

JMM guarantees the visibility of memory by controlling the interaction between the main memory and the local memory of each thread.

3. Reorder

When executing the program, the compiler and processor will reorder the instructions to improve performance.

  • There are 3 types of reordering:
  1. Compiler optimized reordering.
    The compiler can rearrange the execution order of statements without changing the semantics of single-threaded programs.
  2. Instruction-level parallel reordering.
    Instruction-level parallel processing technology can overlap multiple instructions. If there is no data dependency, the processor can change the execution order of the machine instructions corresponding to the statements.
  3. Reordering of the memory system.
    The processor may use caches and read/write buffers, which makes load and store operations appear to be executed out of order.

Reordering process from source code to final execution

These reordering may cause memory visibility problems in multithreaded programs:
1. Compiler reordering (type 1) rules can prohibit specific types of compiled reordering.
2. Processor reordering (types 2 and 3), JMM's processor reordering rules require the compiler to insert specified memory barrier instructions (Memory Barriers) when generating instruction sequences to prohibit instruction reordering.

  • Data Dependency
    When reordering, the compiler and processor will comply with data dependence and will not reorder operations with data dependence.
    Data dependence

  • as-if-serial semantics
    No matter how reordering (execution order or parallel processing), the execution result of the program cannot be changed. The compiler, runtime, and processor must all obey this semantics.
    Because of this semantics, programmers have created an illusion: the thread is executed in the order of the program, but it will actually be optimized and parallel, but it will comply with the happens-before principle. That is to say, this semantic makes it unnecessary to worry about reordering and visibility issues when coding in a thread.

4. Memory Interaction and Memory Barrier

The Java memory model defines 8 operations to complete the interactive operation of main memory and working memory.

8 operations of the memory model

  • read (read): Act on the main memory variable, transfer a variable value from the main memory to the thread's working memory for use in subsequent load actions
  • Load: A variable acting on 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: A variable acting on the working memory. The value of a variable in the working memory is passed to the execution engine. This operation will be executed whenever the virtual machine encounters a bytecode instruction that needs to use the value of the variable.
  • Assign (assignment): A variable acting on the working memory. It assigns a value received from the execution engine to a variable in the working memory. This operation is executed whenever the virtual machine encounters a bytecode instruction for assigning a value to the variable.
  • store: 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.
  • write: A variable acting on the main memory, which transfers the value of a variable in the working memory to a variable in the main memory from the store operation.
  • lock: A variable that acts on the main memory and identifies a variable as a thread exclusive state.
  • Unlock (unlock): Act on the main memory variable, release a variable in a locked state, and the released variable can be locked by other threads.

The Java memory model also stipulates that when performing the above eight basic operations, the following rules must be met:

  • One of read and load, store and write operations is not allowed to appear separately
  • A thread is not allowed to discard its latest assign operation, that is, the variable must be synchronized to the main memory after being changed in the working memory.
  • A thread is not allowed to synchronize data from working memory back to main memory for no reason (no assign operation has occurred).
  • 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 the use and store operations on a variable, the assign and load operations must be performed.
  • A variable can only be locked by one thread at a 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 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
  • 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.
  • Before performing an unlock operation on a variable, the variable must be synchronized to the main memory (execute store and write operations).

These 8 types of memory access operations are very cumbersome, and an equivalent judgment principle will be used later, that is, the happens-before principle to determine whether a memory access is safe in a concurrent environment.
According to the happens-before principle, in a multithreaded environment, a memory barrier needs to be inserted to ensure the safety of synchronization.

  • JMM divides memory barrier instructions into 4 categories:
    Memory barrier type

StoreLoad Barriers is an all-round barrier, it also has the effect of the other 3 barriers.

5. The happens-before principle

  • JMM uses the happens-before concept to illustrate the memory visibility between operations.
    If the execution result of one operation is visible to another operation, then there must be a happen-bofore relationship between the two operations. These two operations can refer to the same thread or different threads.

  • This principle is very important. It is the main basis for judging whether there is competition in the data and whether the thread is safe. Relying on this principle, all the problems of whether there may be conflicts between two operations in a concurrent environment can be solved in a package with several rules.

  • JMM divides the reordering prohibited by happens-before requirements into the following two categories:

    1. Will change the reordering of program execution results, JMM requires the compiler and processor to prohibit this reordering
    2. Will not change the reordering of the program execution results, JMM does not require
      JMM design diagram
  • This design can not only ensure that threads are synchronized clearly according to happens-before when coding, but also enable the compiler and processor to optimize the program to the maximum (reordering, as long as it does not affect the result). Such as lock elimination and other optimization operations.

  • happens-before rule

    1. Single thread principle Single the Thread rule :
      in a thread, the front operating procedures preceding occurs subsequent operations.
    2. Tube-lock rules Monitor Lock Rule
      a unlock operation occurs after the first face lock with a lock operation
    3. volatile variable rules Volatile Variable Rule
      face of this variable after a volatile variable write operations occur in the first read operation
    4. Rules threads started the Thread Start Rule
      Start the Thread object () method is called first occurrence in this thread every action
    5. Threads are rules Thread Join Rule
      the end of the Thread object first occurred in the join () method returns
    6. The Thread Interruption Rule
      calls the thread interrupt() method first when the code of the interrupted thread detects the occurrence of an interrupt event. The interrupted() method can be used to detect whether there is an interrupt.
    7. Object finalization rule Finalizer Rule
      a complete object initialization (constructor executes end) first occurred in its finalize () method starts
    8. Transitivity Transitivity
      If A ahead operation occurs in Procedure B, B ahead operation occurs in operation C, the operation occurs in the first operation A C.

Reference:
Java concurrency
in-depth understanding

Guess you like

Origin blog.csdn.net/u014099894/article/details/102811564