My understanding of volatile keyword

There is a relatively obscure keyword in Java volatile, about which I will talk about my own understanding.
volatileGenerally speaking, there are two functions:

  1. Ensure that the modified variable is immediately visible to other threads
  2. Disable instruction reordering

What is immediately visible? We all know that the memory is much faster than the hard disk, and the cpu is much faster than the memory. What should I do if the efficiency of these three is seriously inconsistent when performing IO operations? Therefore, part of the data will be loaded from the hard disk and cached into the memory, and the memory will cache part of the data into the CPU cache to ensure the efficiency of IO.
Then in a multi-threaded environment, each thread has a cache for a variable in the cpu, and the modification of the variable by a thread is only valid in this thread, and other threads read the original cached value, and the volatilekeyword This problem can be avoided.
volatileAn instruction will be added to the generated bytecode lock. This instruction has two functions:

  1. Write the current processor cache back to memory
  2. Invalidate the cpu cache of other threads

In this way, when other threads read the variable again, they will go back to the memory to get the latest value.
Take a chestnut:

public class VolatileDemo {

    public volatile boolean run = false;

    public static void main(String[] args) {
        VolatileDemo v2 = new VolatileDemo();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + (i + 1));
            }
            v2.run = true;
        }).start();

        new Thread(() -> {
            while (!v2.run)
                ;
            System.out.println(Thread.currentThread().getName() + " is running..");
        }).start();
    }

}

In this code, two threads are started. The first thread loops ten times and changes the volatile-modified boolean variable run to true. The second thread spins until run becomes true, and then prints the following line. Output result:

Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-1 is running..

volatileCompared with synchronizedCan be regarded as a lighter-weight lock, it is more efficient. You may be wondering, volatilewhy do we need to have it if we have it synchronized? One problem with volatile is that it cannot guarantee atomicity. For operations like i++, volatile cannot guarantee atomicity.

Then let's talk about its second role, prohibiting reordering of instructions.
Let's start with an old-fashioned topic, how do you usually write a singleton pattern? Hungry, lazy, or the legendary double-check lock?
Lazy thread is not safe, but it can do lazy loading and save resources.
Hungry-style thread-safe, but can not delay loading, will consume a relatively large amount of resources.
Then the double check lock is equivalent to combining the advantages of the above two, adding a synchronization lock when lazy loading, as follows:

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

Note that uniqueInstance is modified with volatile, which is necessary here, because uniqueInstance = new Singleton(); This operation is actually carried out in three steps:

  1. open up a piece of memory
  2. Initialize the object
  3. point a reference to a memory address

However, because the JVM has the characteristics of instruction rearrangement, it is possible that the execution order becomes 1>3>2, which is naturally no problem in the case of single thread. But if it is multi-threaded, it is possible that other threads get an object that has not been initialized so that the program fails.
Therefore, the purpose of using volatile modification is to prohibit the reordering of JVM instructions and ensure normal operation in a multi-threaded environment.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324891077&siteId=291194637