Java volatile keyword puzzles

Reprinted from: https://www.cnblogs.com/a31415926/p/6744485.html

  When a shared variable is modified by volatile, it will guarantee that the modified value will be updated to the main memory immediately", how is the "guarantee" here done? Is it related to the specific compiled CPU instructions of JIT?

  volatile feature

  Memory visibility: Generally speaking, the modification of a volatile variable by thread A is visible to other threads, that is, each time the thread obtains the value of the volatile variable, it is the latest.

  volatile usage scenarios

  The keyword sychronize can prevent multiple threads from entering the same piece of code. In some specific scenarios, volatile is equivalent to a lightweight sychronize, because it will not cause context switching of threads, but two conditions must be met when using volatile:

  1. The write operation to the variable does not depend on the current value, such as executing a++ under multi-threading, it is impossible to ensure the accuracy of the result through volatile;

  2. The variable is not included in the invariant with other variables. This sentence is a bit awkward, and it is more intuitive to look at the code.

public class NumberRange {
  private volatile int lower = 0;

  private volatile int upper = 10;

  public int getLower() { return lower; }

  public int getUpper () {return upper; }

  public void setLower(int value) {

  if (value > upper)

  throw new IllegalArgumentException(...);

  lower = value;

  }

  public void setUpper(int value) {

  if (value < lower)

  throw new IllegalArgumentException(...);

  upper = value;

  }

  }

 

  In the above code, the upper and lower bounds are initialized to 0 and 10 respectively. It is assumed that threads A and B execute setLower(8) and setUpper(5) at the same time at a certain time, and both pass the invariant check and set an invalid range. (8, 5), so in this scenario, it is necessary to ensure that only one thread can execute the methods setLower and setUpper at each moment through sychronize.

  Here are two scenarios where we often use the volatile keyword in our projects:

  1. Status mark amount

  In a high concurrency scenario, through a boolean variable isopen, how to control whether the code follows the promotion logic?

public class ServerHandler {

  private volatile isopen;

  public void run() {

  if (isopen) {

  //promotion logic

  } else {

  //normal logic

  }

  }

  public void setIsopen(boolean isopen) {

  this.isopen = isopen

  }

  }

 

  The details of the scene do not need to be too entangled. Here is just an example to illustrate the use of volatile. The user's request thread executes the run method. If you need to start a promotion, you can set it through the background. The specific implementation can send a request, call the setIsopen method and set isopen as true, since isopen is modified by volatile, once it is modified, other threads can get the latest value of isopen, and the user request can execute the promotion logic.

  2、double check

  An implementation of the singleton pattern, but many people ignore the volatile keyword, because without this keyword, the program can run well, but the stability of the code is not always 100%, maybe in the future In time, the hidden bug came out.

class Singleton {

  private volatile static Singleton instance;

  public static Singleton getInstance() {

  if (instance == null) {

  syschronized(Singleton.class) {

  if (instance == null) {

  instance = new Singleton();

  }

  }

  }

  return instance;

  }

  }

 

  However, in the implementation of many singleton patterns, I recommend the elegant writing method of lazy loading Initialization on Demand Holder (IODH).

public class Singleton {

  static class SingletonHolder {

  static Singleton instance = new Singleton();

  }

  public static Singleton getInstance(){

  return SingletonHolder.instance;

  }

  }

 

 

  How to guarantee memory visibility?

  In the memory model of the Java virtual machine, there are the concepts of main memory and working memory. Each thread corresponds to a working memory and shares the data of the main memory. Let's take a look at the difference between operating ordinary variables and volatile variables:

  1. For ordinary variables: the read operation will preferentially read the data in the working memory. If it does not exist in the working memory, a copy of the data will be copied from the main memory to the working memory; the write operation will only modify the copy data of the working memory. In this case, other threads cannot read the latest value of the variable.

  2. For volatile variables, JMM will set the corresponding value in the working memory to be invalid during the read operation, requiring the thread to read data from the main memory; during the write operation, JMM will refresh the corresponding data in the working memory to the main memory, In this case, other threads can read the latest value of the variable.

  The memory visibility of volatile variables is based on the memory barrier (Memory Barrier). What is a memory barrier? A memory barrier, also known as a memory barrier, is a CPU instruction. When the program is running, in order to improve the execution performance, the compiler and the processor will reorder the instructions. In order to ensure the same results on different compilers and CPUs, the JMM prohibits specific types of memory barriers by inserting specific types of memory barriers. Compiler reordering and processor reordering, inserting a memory barrier tells the compiler and CPU that no instruction can be reordered with this Memory Barrier instruction.

The code example is as follows :

class Singleton {

  private volatile static Singleton instance;

  private int a;

  private int b;

  private int b;

  public static Singleton getInstance() {

  if (instance == null) {

  syschronized(Singleton.class) {

  if (instance == null) {

  a = 1; // 1

  b = 2; // 2

  instance = new Singleton(); // 3

  c = a + b; // 4

  }

  }

  }

  return instance;

  }

  }

 

 

  1. If the variable instance is not modified with volatile, statements 1, 2, and 3 can be reordered and executed at will, that is, the instruction execution process may be 3214 or 1324.

  2. If it is a variable instance modified by volatile, a memory barrier will be inserted before and after statement 3.

  By observing the assembly code generated by volatile variables and ordinary variables, it can be found that a lock prefix instruction will be added when operating volatile variables:

  Java code :

 instance = new Singleton();

 

  Assembly code :

0x01a3de1d: movb $0x0,0x1104800(%esi);

0x01a3de24: **lock** addl $0x0,(%esp);

 

  This lock prefix instruction is equivalent to the memory barrier described above, providing the following guarantees:

  1. Write the data of the current CPU cache line back to main memory;

  2. This write-back operation will invalidate the data cached at the memory address in other CPUs.

  In order to improve the processing performance, the CPU does not directly communicate with the memory, but reads the data in the memory to the internal cache (L1, L2) and then operates, but after the operation, it cannot determine when it will be written back to the memory. When the variable is written, when the CPU executes the Lock prefix instruction, it will write the data of the cache line where the variable is located back to the memory, but there is still a problem. Even if the data in the memory is the latest, other CPUs still cache the old value. Therefore, in order to ensure the cache consistency of each CPU, each CPU checks the validity of its own cached data by sniffing the data propagated on the bus. When it finds that the data of the memory address corresponding to its own cache line is modified, it will cache the data. The line is set to an invalid state. When the CPU reads the variable and finds that the cache line is set to be invalid, it will re-read the data from the memory to the cache.

Guess you like

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