Java memory model JMM four volatile keyword

foreword

The volatile keyword is the most lightweight synchronization mechanism provided by the JVM, but because it is not easy to be correctly and completely understood, many programmers are accustomed to not using it, but always choose the synchronized heavyweight locking mechanism To synchronize, this article will clarify the real semantics of the volatile keyword.

When a variable is defined as volatile, it will have two semantic properties: visibility and ordering .

1. Visibility

When a thread modifies the value of a volatile variable, the new value is immediately known to other threads. This is not guaranteed by ordinary variables. From the previous JMM memory model, we can see that the values ​​of ordinary variables cannot be synchronized between threads at any time because each thread has its own working memory.

       This is where volatile cannot be easily understood. Although the reading of a volatile variable can guarantee the last write to the volatile variable by any thread, compound operations on volatile variables (such as volatile++, volatile = volatile * x) is still not atomic , because the compound operation of volatile++ actually includes three operations: read, add 1, and write back the result of adding 1. The volatile keyword can only guarantee that the result of the "read" of the first operation is correct, but when the next two operations are performed, other threads can still or even have changed the value of the volatile variable, so that the volatile variable currently operated already expired data. V o latile can only guarantee that a single read or write operation on the modified variable is atomic (including long and double types).

       Therefore, in operation scenarios that do not meet the following two rules, we still need to lock to ensure the atomicity of operations on volatile variables.

          1. The result of the operation does not depend on the current value of the variable, or can ensure that only a single thread modifies the value of the variable.

          2. Variables do not need to participate in invariant constraints with other state variables.

The following scenario is very suitable for using volatile variables to control concurrency. When the shutdown() method is called, it is guaranteed that the doWork() method executing in all threads can be stopped immediately.

 

volatile boolean shutdownRequested;

public void shutdown(){
      shutdownRequested = true;
}

public void doWork(){
      while(!shutdownRequested){
             //do stuff
      }
}

 

 

In general, when a variable is modified by volatile, it means that the thread-local memory is invalid. When a thread modifies the shared variable, it will be updated to the main memory immediately. When other threads read the shared variable, it will directly change from read from main memory. 

 

2. Orderly

The ordering of volatile variables is guaranteed by disabling instruction reordering optimization ( the semantics of volatile masking instruction reordering were not fully repaired until JDK1.5. In previous JDKs, variables were declared volatile in time, but reordering still could not be completely avoided. caused problems ). From the previous reordering content, we know that ordinary variables can only guarantee that all places that depend on the assignment result of the variable during the execution of the method can obtain the correct result by disabling reordering, and if the assignment operation of the variable is not executed by the following The operation depends on, because the change of the variable value cannot be perceived during the execution of the method, so it can be reordered at this time, that is, the behavior described by the as-if-serial semantics. The following code example illustrates why instruction reordering may interfere with concurrent execution of a program:

Map configOptions;

// This variable must be defined as volatile
volatile boolean initialized = false;
// Assume the following code is executed in thread A
// Simulate reading the configuration information. After the reading is completed, set initialized to true to notify other threads that the configuration is available

configOptions  = readConfigOptions(fileName);
initialized = true;

// Assume the following code is executed in thread B
// Wait for initialized to be true, which means that thread A has initialized the configuration information
while(!initialized){
    sleep();
}

// Use the configuration information initialized by thread A
doSomethingUseConfig();

 

If the initialized variable is not defined as volatile, the last sentence of "initialized = true" may be executed in advance due to the optimization of instruction reordering, so the code using configuration information in thread B may have errors, and the volatile keyword can be avoided. such a situation

 

3. The underlying implementation principle

From the second memory barrier chapter of JMM, we can also know that memory barriers can prohibit instruction reordering and affect data visibility. Isn't this the two-layer semantics of the volatile keyword? In fact, the bottom layer of the JVM uses "memory barriers" to implement the semantics of volatile. JMM has formulated the following volatile reordering restriction strategy for the compiler:  
Can it be reordered second operation
 first action  Normal read/write  volatile read  volatile write
 Normal read/write      NO
 volatile read  NO  NO  NO
 volatile write    NO  NO

 

  1. When the first operation is a volatile read, no reordering is possible regardless of the second operation.
  2. When the second operation is a volatile write, no reordering is possible regardless of what the first operation was.
  3. Reordering is also not possible when the first operation is a volatile write and the second operation is a volatile read or write.
        In order to implement the above strategy, the compiler inserts memory barriers in the instruction sequence to prohibit specific types of processor reordering when generating bytecode. The following is the JMM memory barrier insertion strategy based on the conservative strategy (in practice, as long as it does not change volatile write-read memory semantics, the compiler can optimize according to the specific situation, omitting unnecessary barriers):  

Guess you like

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