3

First, through the program to see the phenomenon

Before we start to explain the Java multi-threaded cache model for you, let's look at the following piece of code. The logic of this code is very simple: the main thread starts two child threads, one thread 1 and one thread 2. Thread 1 executes first, and thread 2 executes after sleep for 2 seconds. The two threads use a shared variable shareFlag with an initial value of false. If shareFlag is always equal to false, thread 1 will always be in an infinite loop, so we set shareFlag to true in thread 2 .

 public class VolatileTest {

  public static boolean shareFlag = false;

  public static void main(String[] args) throws InterruptedException {
    new Thread(() -> {
      System.out.print("开始执行线程1 =>");
      while (!shareFlag){  //shareFlag = false则一直死循环
        //System.out.println("shareFlag=" + shareFlag);
      }
      System.out.print("线程1执行完成 =>");
    }).start();

    Thread.sleep(2000);

    new Thread(() -> {
      System.out.print("开始执行线程2 =>");
      shareFlag = true;
      System.out.print("线程2执行完成 =>");
    }).start();
  }

}

If you haven't learned the JMM threading model, maybe after reading the above code, the output you hope to get is as follows:

 开始执行线程1 =>开始执行线程2 =>线程2执行完成 =>线程1执行完成=>

As shown in the figure below, a normal person understands this code. First, execute thread 1 to enter the loop, thread 2 modifies shareFlag=true, and thread 1 jumps out of the loop. So thread 1 that jumps out of the loop will print "Thread 1 execution completed =>", but after the author's experiment, "Thread 1 execution completed =>" will not be printed, and thread 1 does not jump out of the infinite loop , why is this?

2. Why does this phenomenon occur (JMM model)?

To explain the problems mentioned above, we need to learn the JMM (Java Memory Model) Java memory model. I think it is more accurate to call it the Java multi-threaded memory model.

  • First of all, in JMM, each thread has its own working memory. When the program starts, the thread loads (read&load) the shared variable into its own working memory, and the memory variable loaded into the thread working memory is the shared variable in the main memory. copy of . That is to say, there are three copies of shareFlag in memory at this time, and the values are all equal to false.
  • When thread 2 executes shareFlag=true , its working memory copy is modified to shareFlag=true , and the value of the copy is synchronously written back (store&write) to the main memory.
  • But the working memory of thread 1 shareFlag=false has not changed, so thread 1 has been in an infinite loop .

3. MESI Cache Coherence Protocol

According to the above experiment and the JMM model, the value of the shared variable modified by thread 2 is not perceived by thread 1. So how can we make thread 1 aware that the value of the shared variable has changed? In fact, it is very simple, just add the volatile keyword to the shareFlag shared variable.

 public volatile static boolean shareFlag = false;

The underlying principle is this, plus the volatile keyword prompts JMM to follow the MESI cache coherence protocol, which includes the following cache usage specifications ( you can ignore it if you don't understand it, and it will be described in simple language and examples below ).

  1. Modified : Represents that the data of the current Cache line is modified (Dirty), and is only modified in the Cache of the current CPU; at this time, the data of this Cache line is different from the data in other Caches, and is different from that of the line in memory. The data is also different.
  2. Exclusive : The data representing the current Cache row is valid data, and there is no such row of data in the Cache of other CPUs; and the current Cache row data is the same as the data in memory.
  3. Shared : This row of data is cached in the Cache representing multiple CPUs, and the data in the Cache is consistent with the data in the memory;
  4. Invalid : Indicates that the data in the current Cache row is invalid;

The cache usage specification above may be too complicated. Simply put, it is

  • When thread 2 modifies shareFlag (refer to Modify), tell the bus that I have modified the shared variable shareFlag,
  • Thread 1 monitors the Bus bus, and when it learns that the shared variable shareFlag has been modified, it deletes the copy of shareFlag in its working memory to make it invalid.
  • When thread 1 needs to use shareFlag again, it finds that there is no copy of the shareFlag variable in the working memory, and it will be reloaded from the main memory (read&load)

Recommended reading "Concurrent Programming Column"

Welcome to my blog, more boutique knowledge collection

This article is reproduced to indicate the source (must be connected, not just text): Antetokounmpo blog-zimug.com

If you find it helpful, please like and share! Your support is my inexhaustible creative motivation! . In addition, the author has recently output the following fine content, looking forward to your attention.


字母哥博客
933 声望1.5k 粉丝