More wonderful articles, please pay attention to xhJaver, JINGdong engineers and you grow together

Volatile profile

Used to decorate shared variables to ensure visibility and to disallow reordering of instructions

  • When multiple threads operate on the same variable, after one thread modifies the value, other threads can immediately see the modified value, ensuring the visibility of the shared variable

  • Prohibition of instructions to rearrange, to ensure the order of code execution

  • Atomicity is not guaranteed, such as the usual i++

    (But atomicity is guaranteed for a single read or write)

Visibility code sample

The following code is recommended to use the PC side to view, copy paste run directly, all have detailed comments

Let’s write code that tests whether or not volatile variables are needed when multiple threads modify shared variables

  1. First, we create a task class

public class Task implements Runnable{
    @Override
    public void run(a) {
        System.out.println("It is"+Thread.currentThread().getName()+"Thread start,flag is"+Demo.flag);
 // When the shared variable is true, it is always stuck, and does not print the following sentence  // If flag is false, output the following statement  while (Demo.flag){   }  System.out.println("It is"+Thread.currentThread().getName()+"Thread ends,flag is"+Demo.flag);  } } Copy the code

2. Next, we create a test class


class Demo {

    // Share variables without using volatile
    public static   boolean flag = true ;
 public static void main(String[] args) throws InterruptedException {  System.out.println("It is"+Thread.currentThread().getName()+"Thread start,flag is"+flag);  // Start the thread  new Thread(new Task()).start();  try {  // Sleep for a second to make sure that the thread is already in the while loop  // Otherwise, the main thread changes flag to false before it reaches the while loop  Thread.sleep(1000L);  } catch (InterruptedException e) {  e.printStackTrace();  }  Change the shared variable flag to false  flag = false;  System.out.println("It is"+Thread.currentThread().getName()+"Thread ends,flag is"+flag);  } }  Copy the code

3. Let’s look at the output

We changed the shared variable flag to false on the main thread, but the other thread didn’t notice the change, so the change is not visible to the other thread

  • If you use volatile variables, the result will look something like this

public static volatile boolean flag = true

This time, the main thread changes the variable is sensed by another thread, ensuring the visibility of the variable

Visibility principle analysis

So, what’s the magic of volatile at the bottom that you can’t escape? Why not use it to modify the variable so that the other thread can’t see the change?

To answer this question, we first need to look at the JMM(Java Memory Model)

Note: Local memory is an abstraction of the JMM and does not exist. Local memory covers caches, write buffers, registers, and other hardware and compiler optimizations for a place to store data

  • From this we can analyze that the main thread has changed the variable, but the other threads do not know, there are two cases

    1. The variable changed by the main thread has not yet been flushed to main memory, and the other thread reads the previous variable
    2. Variables modified by the main thread are flushed to main memory, but the local copies are read by other threads
  • We can do two things when we use the volatile keyword to modify shared variables

    1. When a thread modifies a variable, it is forcibly flushed into main memory
    2. When a thread reads a variable, it forces the variable to be read from main memory and flushed into working memory

Instruction rearrangement

  • What is reordering?

To make a program run more efficiently, compilers and cpus have to rearrange the order in which code is executed, which can sometimes cause problems

So let’s look at the code


// Command reorder test
public class Demo2 {
    
    private Integer number = 10;
 private boolean flag = false;  private Integer result = 0;   public void write(a){  this.flag = true; // L1  this.number = 20; // L2  }   public void reader(a){  while (this.flag){ // L3  this.result = this.number + 1; // L4  }  } } Copy the code

Let’s say we have two threads, A and B, which execute write() and reader() methods respectively. The order of execution might look something like the following

  • Problem analysis: As can be seen from the figure, the execution order of L2 and L1 of thread A has been reordered. If this is done, when L2 is finished by A, B starts to execute L3, but flag is still false at this time, then L4 cannot be executed, so the value of result is still the initial value of 0, not changed to 21, resulting in the error of program execution

At this point, we can use the volatile keyword to solve the problem

private volatile Integer number = 10;

  • In this case, L1 must execute before L2

In order to implement the memory semantics of volatile, the compiler inserts A memory barrier into the instruction sequence when generating bytecode to prevent certain types of processor reorders

The memory barrier

What is the memory barrier? There are four types of memory barriers. They are

  1. LoadLoad barrier:

    • Load1 LoadLoad Load2 Ensures that the data on Load1 is loaded before Load2 and all subsequent load instructions are loaded
  2. LoadStore barrier:

    • Load1 LoadStore Store2 Ensures that data on Load1 is loaded before Store2 and all subsequent store instructions are stored
  3. StoreLoad barrier:

    • Store1 StoreLoad Load2 Ensures that Store1’s data is visible to other processors (flushed into memory) before Load2 and all subsequent load instructions are loaded
  4. StoreStore barrier:

    • Store1 StoreStore Store2 Ensures that Store1 data is visible to other processors (flushed to memory) before Store2 and all subsequent store instructions are stored

    StoreLoad is an all-purpose barrier that has the effects of the other three barriers. This barrier is expensive to implement because the processor usually has to Fully Flush the current write Buffer into memory.

  • Int a = load1 (load1)
  • Storestore is to write store1 = 5 (store1 store)

Volatile and memory barriers

So what does volatile have to do with these four memory barriers, and how exactly is it inserted?

  1. Volatile writes (insert barriers before and after)

    • Insert a StoreStore barrier in front
    • A StoreLoad barrier is inserted later
  2. Volatile reads (insert barrier only after)

    • Insert a LoadLoad barrier behind it
    • Insert a LoadStore barrier behind

Here’s the official form

Let’s go back and look at our program

  this.flag = true; // L1
  this.number = 20; // L2
Copy the code

Since number was modified by volatile, L2 was written by volatile, so this is what the barrier should look like

  this.flag = true; // L1
  // StoreStore Ensures that the flag data is visible to other processors (refreshed into memory) before the number and all subsequent store instructions are stored
  this.number = 20; // L2
  // StoreLoad ensures that the number data is visible to other processors (flushed into memory) before any subsequent store instructions are loaded
Copy the code

So the execution order of L1 and L2 is not reordered

Ps: Headquarters building 4 is getting better and better. Reward yourself with a cup of milk tea

More wonderful, please pay attention to the public number xhJaver, JINGdong engineers and you grow together

Past wonderful

  • ? Why can thread pool reuse, I am masked circle.
  • Actual combat! XhJaver actually optimizes with thread pools…
  • Oh, no, you’re the one who explained thread pools so clearly?
  • Mysql can rely on index, but I can only rely on work, come on, work!
  • So you are such xhJaver! (Heart-centered text)
  • Can you answer this interview question?