Say what I said before

As I said at the beginning, we are going to put together some learning documents for Concurrent programming in Java, and this is part four: the volatile keyword. We will focus on the main functions of the volatile keyword and how they are implemented.

On the whole

To summarize, the volatile keyword modifies variables. Volatile modifies variables for two purposes

  • The first is to ensure that variables are visible across threads
  • The second is to ensure that operations on variables are not reordered.

Tips: I personally feel that understanding the problem being solved is key with regard to the volatile keyword. Understand what the problem is to solve, then in the process of moving bricks in the future, we must pay attention to these problems. So IN this article I also focus on these two issues. As for how volatile solves these two problems, I think….. It’s not the point. So I use images in a way that makes sense to you. If you must study the underlying principle, to view assembly instructions, you can also private message me. Let me discuss…

okkkkkk.

To address these two questions, we need to understand a few others.

The problem

First problem: the problem that variables are not visible between threads

Let’s look at the code

Description: Prepare two threads, thread A loop using A public variable, when the variable is the specified value exit. Thread B modifies the public variable to the specified value after thread A starts. In theory, thread A should exit the loop, but in practice…….

package com.qidian;

import java.util.concurrent.TimeUnit;

/ * * *@authorProgrammers in wigs@companyJiangsu Ji engraved knowledge - starting point programming */
public class VolatileDemo {
    // Public variables
    private static int x = 3;
    public static void main(String[] args) throws InterruptedException {
        // Thread A loop access variable
        new Thread(()->{
            System.out.println("Thread A starts");
            while(true) {if(x == 4) {break;
                }
            }
            System.out.println("Thread A exits");
        }).start();
        Thread B waits 10 milliseconds before changing variable x to 4
        TimeUnit.MICROSECONDS.sleep(10);
        new Thread(()->{
            x = 4;
            System.out.println("Thread B changes x to:"+x); }).start(); }}Copy the code

Execution Result:

It is obvious that thread B has changed variable X to 4. But thread A still does not end the loop and does not exit……

This is sufficient to indicate that thread B is modifying the value of variable X behind thread A’s back. This is why variables between threads are not visible.

This problem is eliminated when we use volatile to modify the x variable. (You can try it yourself.)

HMM… The reason for this problem can be explained further.

Second problem: instruction reordering.

All of our programs are ultimately instructions to the CPU, which reorders instructions that have no effect on each other in order to improve the efficiency of their execution. For example, the following program:

int x = 10;
int y = 100;
int z = x + y;
Copy the code

The first and second lines assign x and y, respectively. Swapping the assignment order will not affect the final execution of the program, so the instructions for the two assignments can be reordered. But the third line assignment to z uses x and y, so the third line assignment must be executed after the first and second lines and cannot be reordered.

In the case of multiple threads, instruction reordering can be unexpected.

Take a look at this code:

Prepare four public variables x,y,a,b. Prepare two threads. Thread A assigns x to be 1 and thread A to be y. These two assignments are unrelated and can be reordered. Thread B assigns y to be 1, and thread B to be x. These two assignments are also unrelated and can be reordered.

Then loop through the above operations. After analysis, the values of A and B may be as follows:

Case 1: A = 1,b = 1

Case 2: A = 0,b = 1;

Case 3: A = 1,b = 0;

The impossible situation is: A =0; b=0; But the execution result of my program is……

package com.qidian;

import java.util.concurrent.CountDownLatch;

/ * * *@authorProgrammers in wigs@companyJiangsu Ji engraved knowledge - starting point programming */
public class VolatileDemo1 {
    // Prepare four shared variables
    private static int x,y,a,b;
    public static void main(String[] args) throws InterruptedException {
        // Prepare an infinite loop
        int count = 0;
        while(true){
            x = y = a = b = 0;
            // Prepare a CountDownLatch to ensure that the two threads end before comparing the values of the four variables
            CountDownLatch cd = new CountDownLatch(2);
            // thread A assigns x and y to A
            new Thread(()->{
                x = 1;
                a = y;
                cd.countDown();
            }).start();
            // thread B assigns y first and then x to B;
            new Thread(()->{
                y = 1;
                b = x;
                cd.countDown();
            }).start();
            cd.await();// 1, 1, 1, 1, 1, 1, 1, 1, 1
            System.out.println("The first"+(++count)+"Second execution: x =" + x + " , y = " + y + " , a = " + a + " , b = " + b);
            if(a == 0 && b == 0) {break; }}}}Copy the code

Results:

That is, in thread A or thread B, the order of the two assignment statements is reversed, otherwise A and B would not be zero. (If you don’t understand, please go to my B station to see the video explanation.)

A description of the problem

Let’s start with problem 2: instruction reordering.

There is nothing to be said for this, it is cpu-level manipulation, which is designed to improve the execution efficiency of instructions. If your program is anything like the one I wrote about in the previous chapter, remember to use volatile variables.

With respect to issue 1: variables are not visible between threads, we move on to another issue, the JMM memory model.

JMM memory model

The JMM defines how the Java Virtual Machine (JVM) works in computer memory (RAM). The JVM is the entire computer virtual model, so the JMM belongs to the JVM. From an abstract point of view, JMM defines an abstract relationship between threads and Main Memory: Shared variables between threads are stored in Main Memory, and each thread has a private Local Memory where it stores copies of shared variables to read/write. Local memory is an abstraction of the JMM and does not really exist. It covers caching, write buffers, registers, and other hardware and compiler optimizations.

Generally speaking, the CPU has high execution speed, but the memory reading and writing speed is limited. The CPU needs to read and write data from the memory, so the memory reading and writing speed limits the CPU’s execution speed. A better solution, then, is to add a cache (also known as a local cache) between the CPU and memory. The CPU loads data from memory into the cache and then re-uses it. The last value is not written back to memory until it is fully used.

So what’s the problem?

The problem is that today’s cpus are multi-core, and each core has a cache. Like this one:

Each core can execute one thread. The flow of each thread loading and using shared data:

Read, Load, (use, ASign), Store, write. Use and ASign are operations between the CPU and the cache. This data is not written to memory until the thread terminates.

So thread A in our previous program looked like this:

Thread A loads the value of variable X from memory, and then stores the entire value in its cache, using it in A loop. Thread B modifies the variable x of memory weight without checking whether x in memory has changed. But thread A doesn’t know that. This is why variables are not visible between threads.

How does volatile solve the problem?

Now I get the idea:

  • Shared variables are not visible between threads because the JMM memory model makes it impossible for a CPU to share visible data with other threads while storing it in its own cache.
  • The problem of instruction reordering is what the CPU does to speed up instruction execution.

So what’s the solution? Of course, using volatile to modify these variables or objects.

So what’s the principle?

OKKKKK. Let’s see how it works:

What code, screenshots, proof I skip, I will draw a picture about the principle!

Let’s start with how the first problem works:

If variables are not volatile, data in one thread’s cache is invisible to other threads.

At the big guy’s meeting, Blue and three green all copied a secret document from Yellow. Are carefully reading the document, then the small blue found that there is a problem with the document, so modified the document, and remind the yellow also modified the document but the other three small green do not know this thing, so, the small green they get the secret document is not the most correct version of the.

If volatile variables are used, when a thread writes a variable value back to main memory, the cache consistency protocol immediately tells other cpus to invalidate the variable in their cache, and they fetch the latest variable value from main memory.

The big guy continued the meeting. This time, Huang announced the rules: If I change the document here, you must copy a new secret document from me. So after this: when the small blue revised the document, the revised content is also updated to the yellow where, update the process told the three small green, let them throw away the previous secret documents, and then copy the latest secret documents from the yellow where.

Well!!!!!! This is how variables are visible between threads.

In fact, most of the time I mainly want to understand what the problem is, when writing programs to pay attention to avoid these problems OK!

Again, the solution of the second problem

There’s a term for it: memory barrier.

The technical definition goes something like this: a set of processor instructions that implement sequential restrictions on memory operations.

The way to prevent reordering is that all volatile variables have a memory barrier in place before the assignment, preventing other instructions from crossing it. Er…” Across? “To ensure that the positions of the instructions for obtaining and assigning volatile variables remain the same. The following instructions cannot be exchanged.

It goes something like this:

Where is the toilet… There are two processes for going to the bathroom… Number one: go to the bathroom. Number two: wipe your ass. These two processes should not be messed with.

However, the sledgehammer was already suffering from a little lack of oxygen, so I may not remember the order, so we added a “wall” between the two tasks to prevent him from messing around:

That’s the barrier.

conclusion

Two problems that volatile solves are:

  • Volatile variables are visible between threads.
  • Instructions on volatile variables cannot be reordered.

The cause of these two problems is

  • The JMM memory model causes variables to be invisible between threads.
  • CPU instructions are executed out of order to improve execution efficiency.

Anything else……. What else?? Welcome to add…….

I’m a programmer in a wig at Start Programming at….. Comments are welcome…

Starting point programming – the future for you and me…….