In-depth understanding of the role of the volatile keyword (3)

(3) Java memory model

I talked about some problems that may arise in the memory model and concurrent programming. Let's take a look at the Java memory model, and study what guarantees the Java memory model provides us and what methods and mechanisms are provided in java to allow us to ensure the correctness of program execution when doing multi-threaded programming.

      In the Java virtual machine specification, an attempt is made to define a Java memory model (Java Memory Model, JMM) to shield the memory access differences of various hardware platforms and operating systems, so that Java programs can achieve consistent memory access on various platforms. Effect. So what does the Java memory model stipulate? It defines the access rules of variables in the program, and to a larger extent, it defines the order of program execution. Note that in order to obtain better execution performance, the Java memory model does not restrict the execution engine to use the processor's registers or caches to improve instruction execution speed, nor does it restrict the compiler to reorder instructions. That is to say, in the java memory model, there will also be issues of cache consistency and instruction reordering.

     The Java memory model stipulates that all variables are stored in the main memory (similar to the physical memory mentioned above), and each thread has its own working memory (similar to the previous cache). All operations on variables by threads must be performed in working memory, and cannot directly operate on main memory. And each thread cannot access the working memory of other threads.

ok, for example, there is the following Java code

i  = 10;

The execution thread must first assign a value to the cache line where the variable i is located in its own worker thread, and then write it into the main memory. Instead of writing the value 10 directly into main memory.

So what guarantees does the Java language itself provide for atomicity, visibility, and ordering?

1. Atomicity

In Java, the reading and assignment operations of variables of basic data types are atomic operations, that is, these operations cannot be interrupted, either executed or not executed.

ok give me another chestnut

x = 10;         //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4

Then analyze whether the above statement is an atomic operation? At first glance, some friends may say that the operations in the above four statements are all atomic operations. In fact, only statement 1 is an atomic operation, and the other three statements are not atomic operations.

Statement 1 directly assigns the value 10 to x, which means that the thread executing this statement will directly write the value 10 into the working memory.

Statement 2 actually contains two operations. It first reads the value of x, and then writes the value of x into the working memory. Although the two operations of reading the value of x and writing the value of x into the working memory are both Atomic operations, but together they are not atomic operations.

Similarly, x++ and x = x+1 include 3 operations: read the value of x, add 1, and write a new value.

Therefore, in the above four statements, only the operation of statement 1 is atomic.

In other words, only simple reading and assignment (and must assign a number to a variable, mutual assignment between variables is not an atomic operation) are atomic operations. (However, there is one thing to note here: under the 32-bit platform, the reading and assignment of 64-bit data needs to be completed through two operations, and its atomicity cannot be guaranteed. But it seems that in the latest JDK, the JVM has guaranteed Reading and assigning 64-bit data is also an atomic operation)

As can be seen from the above, the Java memory model only guarantees that basic reading and assignment are atomic operations. If you want to achieve the atomicity of a wider range of operations, you can use synchronized and Lock to achieve it. Since synchronized and Lock can guarantee that only one thread executes the code block at any time, there is naturally no atomicity problem, thus ensuring atomicity.

2. Visibility

For visibility, Java provides the volatile keyword to ensure visibility. When a shared variable is modified by volatile, it will ensure that the modified value will be updated to the main memory immediately, and when other threads need to read it, it will go to the memory to read the new value. However, ordinary shared variables cannot guarantee visibility, because after ordinary shared variables are modified, when they are written into the main memory is uncertain, when other threads read them, the memory may still have the original old value at this time, so Visibility cannot be guaranteed.

In addition, visibility can also be guaranteed through synchronized and Lock. Synchronized and Lock can ensure that only one thread acquires the lock at the same time and then executes the synchronization code, and the modification of the variable will be refreshed to the main memory before the lock is released. So visibility is guaranteed.

3. Orderliness

In the Java memory model, the compiler and processor are allowed to reorder instructions, but the reordering process will not affect the execution of single-threaded programs, but will affect the correctness of multi-threaded concurrent execution.

In Java, you can use the volatile keyword to ensure a certain "order" (the specific principles are described in the next section). In addition, synchronized and Lock can be used to ensure orderliness. Obviously, synchronized and Lock guarantee that one thread executes synchronization code at each moment, which is equivalent to letting threads execute synchronization code sequentially, which naturally guarantees orderliness. In addition, the Java memory model has some innate "orderness", that is, the orderliness can be guaranteed without any means. This is usually called the happens-before principle. If the execution order of two operations cannot be deduced from the happens-before principle, then they cannot guarantee their order, and the virtual machine can reorder them at will.

Let's introduce the happens-before principle in detail below:

  • Program order rule: In a thread, according to the code order, the operation written in the front happens before the operation written in the back
  • Locking rules: an unLock operation occurs first before the lock operation of the same lock
  • The volatile variable rule: a write operation to a variable happens before a read operation on the variable occurs later
  • Transfer rule: If operation A happens before operation B, and operation B happens before operation C, then it can be concluded that operation A happens before operation C
  • Thread start rule: The start() method of the Thread object occurs first in every action of this thread
  • Thread Interruption Rules: The call to the thread interrupt() method happens before the code of the interrupted thread detects the occurrence of an interrupt event
  • Thread termination rules: All operations in the thread occur before the termination detection of the thread. We can detect that the thread has terminated execution by means of the Thread.join() method ending and the return value of Thread.isAlive()
  • Object finalization rule: the completion of an object's initialization happens before the start of its finalize() method

  These 8 principles are excerpted from "In-depth Understanding of Java Virtual Machine".

Let's explain the first 4 rules:

  For the rules of program order, my understanding is that the execution of a piece of program code appears to be ordered in a single thread. Note that although this rule mentions that "the operation written in the front happens first before the operation written in the back", this should be that the order in which the program appears to be executed is executed in the order of the code, because the virtual machine may execute the program code Instruction reordering. Although reordering is performed, the final execution result is consistent with the sequential execution of the program, and it will only reorder instructions that do not have data dependencies. Therefore, in a single thread, program execution appears to be executed in order, which should be understood carefully. In fact, this rule is used to ensure the correctness of the execution result of the program in a single thread, but it cannot guarantee the correctness of the execution of the program in multiple threads.

  The second rule is also easier to understand, that is to say, whether in a single thread or in multiple threads, if the same lock is locked, the lock must be released before the lock operation can continue.

  The third rule is a more important rule, and it is also the content that will be discussed later. The intuitive explanation is that if a thread writes a variable first, and then a thread reads it, then the write operation will definitely happen before the read operation.

  The fourth rule actually reflects the transitivity of the happens-before principle.

References: http://www.cnblogs.com/dolphin0520/

Guess you like

Origin blog.csdn.net/m0_37506254/article/details/82285139