Wukong love learning program ape, independent development of Java learning platform, PMP brush problem small program. Currently, I am majoring in Java, Multithreading, SpringBoot, SpringCloud and K8S. This official account is not limited to sharing technology, but also sharing the use of tools, life insights, reading summary.

omg

This article is also the opening of Java concurrent programming, read a lot of information, but when it is their turn to organize to summarize, found that still need to read several times to fully understand the data. Another important point is that drawing is a great way to impress and test your understanding.

Volatile (Volatile)

Never know how to pronounce this word

[ˈv ɔ lə l] n. [ˈv ɔ lə l] n. [ˈv ɔ lə l] n. Unstable; Explosive; capriciousCopy the code

So what does Volatile do in Java?

What is volatile used for in Java?

  • Volatile is provided by the Java virtual machinelightweightSynchronization mechanism (three features)
    • Guaranteed visibility
    • Atomicity is not guaranteed
    • Order reordering is prohibited

To understand these three features, you must know the Java Memory Model (JMM). What is the JMM?

What is JMM?

This is a carefully summarized Mind map of the Java memory model.

3.1 Why do you need a Java memory model?

Why: Mask memory access differences across hardware and operating systems

The JMM is the Java Memory Model, also known as the Java Memory Model, or JMM. The JMM itself is an abstract concept that does not actually exist. It describes a set of rules or specifications that define how variables in a program (including instance fields, static fields, and elements that make up array objects) are accessed.

3.2 What is the Java Memory Model?

  • 1. Define the access rules for various variables in the program
  • 2. The low-level details of storing variable values in memory
  • 3. The low-level details of fetching variable values from memory

What are the two main types of memory in the Java Memory model?

  • Main memory
    • The object instance data section of the Java heap
    • Memory that corresponds to physical hardware
  • The working memory
    • Part of the Java stack
    • Preferentially store in register and cache

3.4 What is the Java memory model?

Several specifications for the Java memory model:

  • 1. All variables are stored in main memory

  • 2. The main memory is part of the VM memory

  • 3. Each thread has its own working memory

  • 4. The thread’s working memory holds a copy of the variables in main memory

  • 5. Thread operations on variables must be performed in working memory

  • 6. Different threads cannot directly access variables in each other’s working memory

  • 7. The transfer of variable values between threads needs to be completed through main memory

Because the JVM to run the program of the entity is a thread, and every thread creation when the JVM to create a working memory (called the stack space) in some places, the working memory is the private data area each thread, while the Java memory model that all variables are stored in main memory, main memory is Shared memory region, all threads can access, But the thread to the operation of the variable (reading assignment, etc.) must be conducted in the working memory, the first thing to copy the variables from the main memory to their working memory space, and operation, the variable operation to complete before you write variables will be the main memory, cannot be directly operating variables in main memory, working memory storage in various threads of variables in the main memory copy copy, Therefore, different threads cannot access each other’s working memory, and communication (value passing) between threads must be completed through main memory. The brief access process is as follows:

3.5 Three major features of the Java memory model

  • Visibility (when one thread changes the value of a shared variable, other threads are immediately aware of the change)
  • Atomicity (the fact that an operation or series of operations are indivisible and either succeed or fail simultaneously)
  • Orderability (assignment of variables in the same order as execution in the program code)

About order: If viewed from within this thread, all operations are ordered; If you look at another thread in one thread, all operations are out of order. The Semantics of in-thread as-if-serial Semantics and the synchronization Semantics of working memory and main memory are necessary If you are to speak intelligently and intelligentially.

Can you give an example of how to use volatile?

Consider this scenario:

There is an object whose field number is initialized to 0, and this object has a public method setNumberTo100() that sets the number to 100. When the main thread calls setNumberTo100() through a child thread, does the main thread know that the number value has changed?

If the number variable was not defined using volatile, the main thread would not know that the child thread updated the value of number.

(1) Define the object as described above: ShareData

class ShareData {
    int number = 0;

    public void setNumberTo100(a) {
        this.number = 100; }}Copy the code

(2) The main thread initializes a child thread, named child thread

The subline is set to sleep for 3s, and then set number=100. The main thread constantly checks to see if the number value is equal to 0. If not, it exits the main thread.

public class volatileVisibility {
    public static void main(String[] args) {
        / / resource class
        ShareData shareData = new ShareData();

        // The subthread implements a lambda expression for the Runnable interface
        new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "\t come in");

            // The thread sleeps for 3 seconds, assuming it is performing an operation
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Modify the value of number
            myData.setNumberTo100();

            // Output the modified value
            System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number);

        }, "Child thread").start();

        while(myData.number == 0) {
            // The main thread waits until the number value is different from zero
        }

        // It is not possible to print this value because the main thread is running with a value of 0, so it is always looping
        * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
        System.out.println(Thread.currentThread().getName() + "\t main thread sensed number was not equal to 0");

        /** * * update number value:100 * update number value:100 * update number value:100 * update number value:100 * update number :100 * update number :100 * update number :100 * update number :100 * update number :100 * update number :100 * update number :100 * update number :100 * update number :100 * update number :100}}Copy the code

(3) We use volatile to modify number

Class ShareData {// the keyword "volatile" is used to increase visibility between threads. If one thread changes a value in memory, other threads can immediately perceive volatile int number = 0; public void setNumberTo100() { this.number = 100; }}Copy the code

Output results:

The child thread hasinSubthread update number value:100 Main Main thread know that number is not equal to 0 Process Finished withexit code 0
Copy the code

For volatile variables, when one thread updates a variable, other threads are aware of it.

Why can other threads sense variable updates?

In fact, snooping was used here. Before we talk about snooping, let’s talk about cache consistency.

5.1 Cache Consistency

When multiple cpus hold caches from a copy of the same main memory, when another CPU secretly changes the main memory data, the other CPU does not know, the copy memory will be inconsistent with the main memory, this is called cache inconsistency. So how do we make sure that the cache is consistent? This requires the operating system to jointly formulate a synchronization rule to ensure this, and this rule has the MESI protocol.

As shown in the figure below, CPU2 secretly changed the num value to 2, and CPU1 and CPU3 did not know that the num value had changed.

5.2 the msci

When a CPU writes data, if it finds that the variable being operated on is a shared variable, that is, a copy of the variable exists in other cpus, the system signals the other CPUS to set the cache line for the memory variable to invalid. As shown in the following figure, num=1 in CPU1 and CPU3 is invalid.

When the other CPU reads the variable and finds that the cache line in which it cached the variable is invalid, it rereads it from memory.

As shown in the figure below, CPU1 and CPU3 discover that the num value in the cache is invalid and re-read from memory with the num value updated to 2.

5.3 Bus sniffing

How do other cpus know to update the cache to invalid? Bus sniffing is used here.

Each CPU constant sniffing bus transmission of data to check whether their cache value has expired, if the processor finds its own cache line corresponds to the memory address has been changed, and will set the current processor cache line to invalid state, when the processor to modify the data operation, will be the data read from the memory back to the processor cache.

5.4 Bus Storm

What are the disadvantages of bus sniffing?

Due to the MESI cache consistency protocol, constant memory sniffing of the main line is required, and the large number of interactions can cause the bus bandwidth to peak. So don’t abuse volatile. Use locks instead

Can you demonstrate why volatile does not guarantee atomicity?

Atomicity: An operation or series of operations are indivisible and either succeed or fail at the same time.

Volatile (volatile) Volatile (volatile) volatile (volatile) volatile Show me the code!

Consider this scenario:

What is the value of “number” when 20 threads simultaneously increment “number” by 1 1000 times?

In a single threaded scenario, the answer is 20,000, but in a multi-threaded scenario? The answer is probably 20,000, but in many cases it’s less than 20,000.

Example code:

package com.jackson0714.passjava.threads;

/** Demonstrate that volatile does not guarantee atomicity *@create: the 2020-08-13 09:53 * /

public class VolatileAtomicity {
    public static volatile int number = 0;

    public static void increase(a) {
        number++;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    increase();
                }
            }, String.valueOf(i)).start();
        }

        // When all cumulative threads are finished
        while(Thread.activeCount() > 2) { Thread.yield(); } System.out.println(number); }}Copy the code

Execution result: 19144 for the first time, 20000 for the second time, 19378 for the third time.

Let’s examine the increase() method and decompile the javap tool to produce the following assembly code:

  public static void increase(a);
    Code:
       0: getstatic     #2                  // Field number:I
       3: iconst_1
       4: iadd
       5: putstatic     #2                  // Field number:I
       8: return
Copy the code

Number++ actually executes three instructions:

Getstatic: takes the original value of number. Iadd: adds 1. Putfield: writes back the value after increasing 1

The volatile keyword ensures that the number is correct when the getstatic command is executed to fetch the number from the top of the stack, but other threads may have changed the number when the iconst_1 or iadd command is executed. The value at the top of the operation stack becomes obsolete, so it is possible to synchronize smaller number values back into main memory after the putStatic instruction is executed.

Summary:

Even when the number++ line is executed, even if the number variable is volatile, it can be modified by other threads during execution, and atomicity is not guaranteed.

Seven, how to ensure that the output is 20000?

7.1 synchronized Code block

We can ensure atomicity by using synchronized blocks of code. So this is equal to 20,000

public synchronized static void increase(a) {
   number++;
}
Copy the code

But using synchronized is so heavy that it blocks, and only one thread can enter the method. We can use the AtomicInterger toolkit in Java and send packages (JUC).

7.2 AtomicInterger Atomic operation

Let’s look at the AtomicInterger method getAndIncrement()

public static AtomicInteger atomicInteger = new AtomicInteger();

public static void main(String[] args) {

    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                atomicInteger.getAndIncrement();
            }
        }, String.valueOf(i)).start();
    }

    // When all cumulative threads are finished
    while(Thread.activeCount() > 2) {
        Thread.yield();
    }

    System.out.println(atomicInteger);
}
Copy the code

The number of runs is 20,000.

What is the prohibition of reordering?

When you talk about rearrangements you need to know why they’re being rearranged and what kinds of rearrangements there are.

As shown in the following figure, the order of instruction execution is 1>2>3>4. After reordering, the order of execution is updated to 3->4->2->1.

Do you feel like the rearrangement has messed up the order of instructions? Is that good?

Think back to your elementary school math problem: 2+3-5=? , if the operation order is changed to 3-5+2=? , the result is the same. So the instruction reorder is to ensure that the result of the single thread of the program does not change.

8.1 Why the rearrangement

When a computer executes a program, compilers and processors often reorder instructions to improve performance.

8.2 What kinds of rearrangements are available

  • 1. Compiler optimization reorder: The compiler can reorder statements without changing the semantics of a single-threaded program.

  • 2. Instruction-level parallel reordering: Modern processors use instruction-level parallelism to execute multiple instructions on top of each other. If there is no data dependency, the processor can change the order in which statements are executed to the corresponding machine instructions.

  • 3. Memory system reordering: Because the processor uses caching and read/write buffers, it may appear that load and store operations are being performed out of order.

Note:

  • The single-threaded environment ensures that the final execution results are consistent with the results of the code order

  • The processor must consider data dependencies between instructions when reordering

  • In a multithreaded environment, threads are executed alternately. Due to the existence of compiler optimization rearrangements, the consistency of variables used in the two threads is uncertain, and the result is unpredictable.

8.3 What is an example of instruction reordering in multiple threads?

Consider this scenario: If num=0 and flag=false are defined, thread 1 calls init(), thread 1 calls add(), and thread 1 calls num+100 when flag=true. Num =1 and flag=true may be executed in reverse order, so that num may equal 100

public class VolatileResort {
    static int num = 0;
    static boolean flag = false;
    public static void init(a) {
        num= 1;
        flag = true;
    }
    public static void add(a) {
        if (flag) {
            num = num + 5;
            System.out.println("num:"+ num); }}public static void main(String[] args) {
        init();
        new Thread(() -> {
            add();
        },"Child thread").start(); }}Copy the code

First look at the order reorder in thread 1:

num= 1; flag = true; Flag =true; num = 1; , as shown in the following figure

If thread 2 num=num+5 was executed before thread 1 set num=1, then thread 2’s num variable has a value of 5. The timing diagram is shown below.

8.4 How can volatile prevent order reordering?

We use volatile to define the flag variable:

static volatile boolean flag = false;
Copy the code

How to disable order reorder:

How it works: Memory Barries are inserted before and after instruction sequences generated by volatile to prevent processors from reordering.

There are four types of memory barriers:

How to insert memory barriers in volatile write scenarios:

  • Insert a StoreStore barrier (write-write barrier) before each volatile write.

  • Insert a StoreLoad barrier (write-read barrier) after each volatile write.

The StoreStore barrier guarantees that all normal writes (num=1 for num) that precede volatile writes (flag assignment flag=true) will be visible to any processor, ensuring that all normal writes are flushed to main memory before volatile writes.

How to insert a memory barrier in volatile read scenarios:

  • Insert a LoadLoad barrier after each volatile read operation.

  • Insert a LoadStore barrier (read-write barrier) after each volatile read operation.

The LoadStore barrier guarantees that all subsequent normal writes (num=num+5) must be performed after volatile reads (if(flag)).

X. Common use of volatile

Here’s an example of an application, the singleton pattern of double detection locking

package com.jackson0714.passjava.threads;
/** Demonstrate the application of volatile singleton mode (bilateral detection) *@author: Wukong chat structure *@create: the 2020-08-17 * /

class VolatileSingleton {
    private static VolatileSingleton instance = null;
    private VolatileSingleton(a) {
        System.out.println(Thread.currentThread().getName() + "\t I am constructor SingletonDemo");
    }
    public static VolatileSingleton getInstance(a) {
        // The first test
        if(instance == null) {
            // Lock the code block
            synchronized (VolatileSingleton.class) {
                // The second test
                if(instance == null) {
                    // Instantiate the object
                    instance = newVolatileSingleton(); }}}returninstance; }}Copy the code

The code looks fine, but instance = new VolatileSingleton(); Think of it as three pseudocodes:

memory = allocate(); // 1, allocate the object memory space
instance(memory); // 2. Initialize the object
instance = memory; // 3. Set instance to point to the memory address just allocated. = null
Copy the code

There is no data dependency between step 2 and Step 3, and the execution result of the program does not change in a single thread either before or after the reorder, so this reorder optimization is allowed.

memory = allocate(); // 1, allocate the object memory space
instance = memory; // 3. Set instance to point to the memory address just allocated. = null, but the object has not been initialized
instance(memory); // 2. Initialize the object
Copy the code

If (instance == null), return the newly allocated memory address, but the object has not been initialized and the instance it gets is false. As shown in the figure below:

Solution: Define instance as a volatile variable

private static volatile VolatileSingleton instance = null;
Copy the code

Volatile does not guarantee atomicity. Why do we use it?

The odd thing is, volatile doesn’t even guarantee atomicity, so why do we use it?

Volatile is a lightweight synchronization mechanism that has less impact on performance than synchronized.

Typical usage: Check a status flag to determine whether to exit a loop.

For example, when a thread tries to sleep using a traditional method similar to counting sheep, asleep must be a volatile variable in order for the example to execute correctly. Otherwise, when Asleep is modified by another thread, the thread performing the judgment will not notice.

So why don’t we just synchorize it? They guarantee both visibility and atomicity and why not use them?

Because synchorized and lock are exclusive locks (pessimistic locks), if multiple threads need to access the variable, there will be a race. Only one thread can access the variable, and the other threads will be blocked, affecting the performance of the program.

Note: Volatile variables should be used if and only if all of the following conditions are met

  • Writes to a variable do not depend on the current value of the variable, or you can ensure that only a single thread updates the value of the variable.
  • This variable is not included in the invariance condition along with other states.
  • No locks are required to access variables.

The difference between volatile and synchronzied

  • Volatile can only modify instance and class variables; synchronized can modify methods and code blocks.
  • Volatile does not guarantee atomicity; synchronized does
  • Volatile does not block, synchronized may
  • Volatile lightweight locks, synchronized heavyweight locks
  • Both volatile and synchronized guarantee visibility and order

Xiii. Summary

  • Volatile guarantees visibility: when one thread changes the value of a shared variable, other threads know about the change immediately.
  • Volatile guarantees non-reordering of instructions in a single thread: the order of instruction execution is guaranteed by inserting a memory barrier.
  • Volatitle does not guarantee atomicity. The auto-increase operation, such as a++, has concurrent risks, for example, when inventory is deducted or coupons are issued.
  • Volatile 64-bit long and double variables that can be read or written atomically.
  • Volatile can be used as a singleton type of double lock, with better performance than synchronized.
  • Volatile can be used to check a status flag to determine whether to exit a loop.

References:

Understanding the Java Virtual Machine

The Art of Concurrent Programming in Java

Java Concurrent Programming In Action

Looking forward to the next chapter? CAS start!

Hello, I am Wukong Brother, “7 years of project development experience, full stack engineer, development team leader, love schematic programming underlying principles”.

I also handwritten 2 small program, Java brush small program, PMP brush small program, click on my public number menu to open! In addition, 111 architect documents and 1000 Java interview questions are available in PDF format. You can follow the public number “Wukong chat structure” reply to Wukong to get quality information.

“Retweet -> Look -> Like -> Favorites -> Comment!!” Is the biggest support for me!

Java Concurrency Must Know must be series:

1. Counter the interviewer | | 14 zhang diagram is no longer afraid of being asked a volatile!

2. Programmer was despised by his wife in the middle of the night because CAS principle was too simple?

3. The principle of with blocks on ABA | wife incredibly understand again!

4. Cut the finest | 21 picture with you appreciate collection thread unsafe

5.5000 word | 24 picture show you thoroughly understand the 21 kinds of locks in Java

6. Dry goods | reel off 18 kinds of Queue (Queue), the stability

The three years are distributed very miserable pit, exposure 10 hole | 🏆 fifth edition essay technology projects