What are we talking about when we are talking about JMM (Java memory model)

  In the previous articles, we talked about the usage and underlying implementation of synchronized, final, and void, and there is one topic that cannot be avoided - the Java Memory Model (JMM for short). The Java memory model is the basis for ensuring thread safety. It mainly describes the limitations of atomicity, visibility and ordering embodied in the fully ordered synchronization actions in the program when different threads access shared global variables.

1. Definition

  维基百科定义:The Java memory model describes how threads in the Java programming language interact through memory. Together with the description of single-threaded execution of code, the memory model provides the semantics of the Java programming language

  The general idea is that the Java memory model describes how multiple Java threads interact with memory, and as with single-threaded execution, the memory model provides a reasonably correct Java programming semantics in a multi-threaded scenario.

  The JSR133 specification was developed by the JSR133 expert group and first implemented in Java 5.0. The specification describes the semantics of multithreading and memory interaction in detail and accurately, and becomes a part of the Java specification. It improves the errors and ambiguous parts of the original Java semantics and ensures Java cross-platform.

  The definition of the memory model in JSR133 is as follows:

  Given a program and a sequence of execution trajectories for the program, the memory model describes whether the execution trajectory is a legitimate execution of the program. For Java, the memory model examines each read in the execution trace, and then verifies that the write observed by that read is legitimate according to certain rules. The memory model describes the possible behavior of a program. JVM implementations are free to generate the code they want, as long as the results of all eventual executions of the program can be predicted by the memory model. This provides ample freedom for a large number of code transformations, including action reordering and unnecessary synchronization removal.

  A high-level, informal overview of the memory model shows that it is a set of rules that dictate when one thread's writes will be visible to another thread. In layman's terms, a read operation r usually sees the value written by any write operation w, meaning that w did not occur after r, and w does not appear to be overwritten by another write operation w' (from the perspective of r) .

  When the word "read" is used in this memory model specification, it refers only to the action of reading a field or array element. The semantics of other operations, such as reading the length of an array, performing checked conversions, and virtual method calls, are not directly affected by data races. It is the responsibility of the JVM implementation to ensure that data races do not cause incorrect behavior such as returning the wrong array length or calling a virtual method causing a segfault.

  Memory semantics determine the values ​​that can be read at each moment in the program. Actions in each individual thread must appear to be governed by the semantics of that thread, excluding the case where the value seen by the read operation is determined by the memory model. When referring to this scenario, we say that the program obeys intra-thread semantics.

2. JMM approximation model

  In order to facilitate the understanding of JMM, JSR133 proposes an approximate model-Happen-Before memory model, which is a necessary but not sufficient condition for the formal definition. First of all, we need to explain the definition of synchronous action. Synchronous action refers to the locking and unlocking of locks, the reading of void objects, thread actions, and detecting whether the thread ends. Corresponding to the synchronization action is the synchronization edge (synchronize-with edge). The synchronization edge can be understood as a non-overlapping barrier between synchronization actions, including the following points:

  1. An unlock action on a monitor m synchronizes-with all subsequent lock actions on m (where the subsequent actions are defined in terms of synchronization order).
  2. A write to a volatile variable v synchronizes-with all subsequent reads of v by any arbitrary thread (where the subsequent is defined in terms of synchronization order).
  3. The action used to start a thread synchronizes-with the first action in that newly started thread.
  4. The last action of thread T1 synchronizes-with any action in thread T2 used to detect whether T1 is terminated. T2 may do this by calling T1.isAlive() or by performing a join action on T1.
  5. If thread T1 interrupts thread T2, T1's interrupt operation synchronizes-with any other thread (including T2) at any time the operation used to determine whether T2 was interrupted. This can be done by throwing an InterruptedException or by calling Thread.interrupted and Thread.isInterrupted.
  6. Actions that write default values ​​(0, false or null) for each variable synchronizes-with the first action in each thread.
  7. While it may seem odd to write default values ​​for variables in an object before it is allocated, conceptually, programs start creating objects with default initial values. Therefore, the default initialization of any object happens-before any other action in the program (except for writing default values).
  8. When an object's finalizer is called, the object's reference is implicitly read. There is a happens-before edge between the end of an object's constructor and the reading of the reference. Note that all freeze operations for this object start-before the happens-before edge.

  Let's talk about these restrictions in detail. The first one explains that the locking and unlocking of the monitor (synchronized underlying implementation monitor) has a synchronous relationship, including two points. The same thread can repeatedly lock the locked monitor. It is enough to ensure that the number of unlocks is the same. For the monitor process that has been locked, the thread is blocked and suspended. The second write operation of the volatile variable will be reflected immediately after the read operation of the volatile variable by other threads, which is generally implemented through the memory consistency protocol. The cache is invalidated to read the latest modified value in memory. The implementation of these two items has been described in detail in the previous article. The third and fourth items describe that the thread starts before the first action of the thread, and the last action of the thread is before the termination of the thread (inquiry termination??? This is not very understandable), and must not be reordered. The fifth article describes the synchronization of thread A's interrupt operation to thread B and the interrupt detection action of thread B, and the two actions are mutually exclusive. Article 6 and 7 explain that the default value of a variable is before the first action or other operation in the thread. In fact, sometimes the compiler will optimize the default value of the variable but assign it, as long as the variable has not been used. Item 8 explains the initial assignment of object fields in the constructor and the return of object references. Final fields ensure that initialization assignments are made before object references are returned, while non-final fields are not guaranteed.

  The global order of all synchronization actions is called synchronization order, and the synchronize-with edge and program order constitute the Happen-before order, which is the Happen-before memory model. The Java memory model is a subset of the Happen-before memory model, because the Happen-before model often violates causality, and the most fatal weakness is "values ​​appear out of thin air". For the official specification of the Java memory model, please refer to Chapter 7 of JSR133 (I also see it in the clouds--!)

3. Summary

  In general, the concept of memory model was first proposed by the Java language, and it plays a great role in ensuring the integrity and robustness of Java semantics. C/C++ and other languages ​​also refer to such a memory model in the lock multi-thread concurrency model. . Understanding JMM allows us to better understand the logic of interaction with memory in JAVA multi-threaded concurrent execution, so as to write robust and efficient concurrent programs.

  

 

Guess you like

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