This is the 6th day of my participation in the August More Text Challenge

preface

(1)CPUCache model

CPU cache model, illustrated:

Just remember: the CPU reads data from memory through the CPU cache.

(2) Take a lookvolatile

Data from main memory is loaded into the CPU’s local cache, and the CPU reads and writes from its own cache.

Because the CPU cache model, by default, is problematic, especially when multiple threads are running concurrently, causing the local cache of each CPU to be out of sync with main memory.

Here’s an example:

  1. Thread 0: reads from memoryflagIf the value read is different from the value in the thread, output
  2. Thread 1: each1sUpdate memoryflag
public class Test {
    static int flag = 0;
    public static void main(String[] args) {
        Thread 0: reads only
        new Thread(() -> {
            int localFlag = flag;
            while(true) {
                if(localFlag ! = flag) { System.out.println("Read the modified flag bit:" + flag);
                    localFlag = flag;
                }
            }
        }).start();
        // Thread 1: writes every 1s
        new Thread(() -> {
            int localFlag = flag;
            while(true) {
                System.out.println("Flag bit changed to:" + ++localFlag);
                flag = localFlag;
                try {
                    Thread.sleep(1000);
                } catch(Exception e) { e.printStackTrace(); } } }).start(); }}Copy the code

The following output is displayed:

The flag bit is changed as follows: 1 Read the modified flag bit: 1 The flag bit is changed as follows: 2 The flag bit is changed as follows: 3 The flag bit is changed as follows: 4... .Copy the code

Then giveflagaddvolatileWhat happens?

public class Test { static volatile int flag = 0; / /... . }Copy the code

The following output is displayed:

1 The modified flag bit is read: 1 The modified flag bit is read: 2 The modified flag bit is read: 2 The modified flag bit is read: 3 The modified flag bit is read: 3 the modified flag bit is read: 4 The modified flag bit is read: 4... .Copy the code

With volatile, you can read the latest value every time!!

(3)MESIProtocol: Cache consistency protocol

MESI protocol: cache consistency protocol, there is no previous multi-threaded concurrency problem.

Depending on the mechanism is:CPUSniffing mechanism that makes memory flags expire.

How does that work at the bottomMESIThe mechanism?

  • use:lockPrefix instructions: Memory barriers
  • Both read and write add a memory barrier

Illustrated as follows:

By what instructions? The operation is as follows:

  • read: Reads from main memory
  • load: Writes values read from main memory to working memory
  • use: Computes by reading data from working memory
  • assign: reassigns the calculated value to the working memory
  • store: Writes working memory data to main storage
  • writeWill:storePast variable values are assigned to variables in main memory

(4) IllustrationJavaThe memory model

The Java memory model is similar to the CPU memory model.

It’s just that the Java memory model is standardized, masking underlying differences between computers.

Java memory model, illustrated below:

(5) atomicity, visibility and order

Concurrency has three major problems: atomicity, visibility, and order.

So let’s look at volatile from these three issues.

1) Atomicity:volatileno

volatileNot suitable for scenarios where atomicity is required

Such as relying on the original values for updates, most typically in a++ scenarios, volatile alone does not guarantee a++ thread safety.

For example, a++ :

Public class DontVolatile implements Runnable {volatile int a; AtomicInteger realA = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Runnable r = new DontVolatile(); Thread thread1 = new Thread(r); Thread thread2 = new Thread(r); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(((DontVolatile) r).a); System.out.println(((DontVolatile) r).realA.get()); } @Override public void run() { for (int i = 0; i < 100000; i++) { a++; realA.incrementAndGet(); }}}Copy the code

The result is as follows:

187166, 200000,Copy the code

It can be seen that even though variable A was volatile, and even though it ended up performing 200,000 self-add-ons (as evidenced by the final value of the atomic class), some self-add-ons failed, so that its final result was less than 200,000. This proves that volatile does not guarantee atomicity.

2) Visibility:volatilecan

See also: For example: Consider volatile

3) Order:volatilecan

volatileInstruction reordering can be prevented.

Compilers and instructions sometimes reorder instructions to improve code execution efficiency.

If flag = true is set to execute first, thread 2 will skip the while wait and execute some code. As a result, the code logic fails because the prepare() method is not ready.

Second,volatilerole

As a reminder, volatile serves two main purposes:

  1. Guaranteed visibility

The happens-before relationship for volatile is described as follows: writes to a volatile variable happen-before are read to that variable. This means that if a variable is volatile, the variable’s latest value must be read on subsequent readings after each change.

  1. Disallow reordering

As-if-serial: No matter how reordered, the execution of a (single-threaded) program does not change. With as-IF-serial semantics, the actual order in which the code is executed may be different from the order in which it was written due to compiler or CPU optimizations, which is fine in the case of a single thread, but once multithreading is introduced, this out-of-order order can lead to serious thread-safety issues. The use of the volatile keyword prevents this reordering to some extent.

volatileUsage scenarios

1) Applicable situation 1: Boolean marker bit

A typical scenario is the Boolean bit scenario:volatile boolean flag

Used to control multithreading.

public class YesVolatile1 implements Runnable { volatile boolean done = false; AtomicInteger realA = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { Runnable r = new YesVolatile1(); Thread thread1 = new Thread(r); Thread thread2 = new Thread(r); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(((YesVolatile1) r).done); System.out.println(((YesVolatile1) r).realA.get()); } @Override public void run() { for (int i = 0; i < 1000; i++) { setDone(); realA.incrementAndGet(); } } private void setDone() { done = true; }}Copy the code

2) Case 2: As a trigger

Scenario: Act as a trigger to ensure visibility of other variables.

Here’s a classic example from Brian Goetz:

Map configOptions; char[] configText; volatile boolean initialized = false; . . . // In thread A configOptions = new HashMap(); configText = readConfigFile(fileName); processConfigOptions(configText, configOptions); initialized = true; . . . // In thread B while (! initialized) sleep(); // use configOptionsCopy the code