Detailed usage of volatile keyword in java

In order to solve the problems of atomicity, visibility and order in concurrent programming, the Java language provides a series of keywords related to concurrent processing, such as , , , synchronizedand volatileso finalon concurren包. In the [previous][2] article, we also introduced synchronizedthe usage and principle of . In this article, let's analyze another keyword - volatile.

This article revolves around volatileit, mainly introducing volatilethe usage, volatileprinciples, and volatilehow to provide visibility and order guarantees.

volatileThis keyword exists not only in the Java language, but also in many languages, and its usage and semantics are also different. Especially in C language, C++ and Java, there are volatilekeywords. Both can be used to declare variables or objects. The following is a brief introduction to the keywords in the Java language volatile.

usage of volatile

volatileIt is usually compared to "lightweight synchronized", and it is also a more important keyword in Java concurrent programming. And synchronizeddifferent, volatileis a variable modifier, can only be used to modify variables. Methods and code blocks cannot be modified.

volatilevolatileThe usage of is relatively simple, you only need to use modification when declaring a variable that may be accessed by multiple threads at the same time .

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  

The above code is a typical implementation of a singleton in the form of double lock verification, in which volatilekeywords are used to modify the singleton that may be accessed by multiple threads at the same time.

The principle of volatile

In [If someone asks you what the Java memory model is, send him this article][1], we have introduced that in order to improve the execution speed of the processor, a multi-level cache is added between the processor and the memory. to improve. However, due to the introduction of multi-level cache, there is a problem of cache data inconsistency.

However, for volatilevariables, when volatilethe variable is written, the JVM will send a lock prefix instruction to the processor to write the variable in the cache back to the system main memory.

But even if it is written back to the memory, if the values ​​cached by other processors are still old, there will be problems in performing calculation operations. Therefore, under multi-processors, in order to ensure that the caches of each processor are consistent, it will be implemented.缓存一致性协议

Cache consistency protocol : Each processor checks whether the value of its cache has expired by sniffing the data propagated on the bus. When the processor finds that the memory address corresponding to its cache line has been modified, it will update the current processor's The cache line is set to an invalid state. When the processor wants to modify the data, it will be forced to re-read the data from the system memory into the processor cache.

Therefore, if a variable is volatilemodified, its value will be forced to be flushed into main memory after each data change. The caches of other processors will also load the value of this variable from the main memory into their own caches because they comply with the cache coherency protocol. This ensures that volatilea value is visible in multiple caches in concurrent programming.

volatile and visibility

Visibility means that when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can immediately see the modified value.

We analyzed it in [If someone asks you what the Java memory model is, send him this article][1]: The Java memory model stipulates that all variables are stored in the main memory, and each thread has its own The working memory of the thread stores the main memory copy of the variables used in the thread in the working memory of the thread. All operations on variables by the thread must be performed in the working memory, and the main memory cannot be directly read and written. Different threads cannot directly access variables in each other's working memory, and the transfer of variables between threads requires data synchronization between their own working memory and main memory. Therefore, it may happen that thread 1 changes the value of a variable, but thread 2 is not visible.

As mentioned above volatile, the keywords in Java volatileprovide a function, that is, the variables modified by them can be synchronized to the main memory immediately after being modified, and the variables modified by them are reset from the main memory before each use. Main memory is flushed. Therefore, it can be used volatileto ensure the visibility of variables during multi-threaded operations.

volatile and ordered

Sequence means that the order of program execution is executed in the order of code.

We analyzed it in [If someone asks you what the Java memory model is, send him this article][1]: In addition to the introduction of time slices, due to processor optimization and instruction rearrangement, the CPU may also Input code for out-of-order execution, e.g. load->add->savemay be optimized into load->save->add. This is where there may be an orderly problem.

In volatileaddition to ensuring the visibility of data, it also has a powerful function, that is, it can prohibit instruction rearrangement optimization, etc.

Ordinary variables only guarantee that the correct results can be obtained in the places where the assignment results depend on during the execution of the method, but cannot guarantee that the order of variable assignment operations is consistent with the execution order in the program code.

Volatile can prohibit the rearrangement of instructions, which ensures that the program of the code will be executed in strict accordance with the order of the code. This ensures orderliness. The operation of volatilethe modified variable will be executed strictly according to the code sequence, and load->add->savethe execution sequence is: load, add, save.

volatile and atomicity

Atomicity means that an operation is uninterruptible, and it must be executed completely, or it will not be executed at all.

What is going on with our multithreading problem in [Java's concurrent programming? ][3] analyzed: thread is the basic unit of CPU scheduling. The CPU has the concept of time slices, and will perform thread scheduling according to different scheduling algorithms. When a thread starts executing after obtaining the time slice, after the time slice is exhausted, it will lose the right to use the CPU. Therefore, in a multi-threaded scenario, since time slices are rotated between threads, atomicity problems will occur.

In the previous article, synchronizedwhen we introduced it, we mentioned that in order to ensure atomicity, we need to pass bytecode instructions monitorenterand monitorexit, but volatilethere is no relationship between these two instructions.

Therefore, volatileatomicity cannot be guaranteed.

volatileCan be used instead in the following two scenarios synchronized:

1. The result of the operation does not depend on the current value of the variable, or it can ensure that only a single thread will modify the value of the variable.

2. Variables do not need to participate in invariant constraints with other state variables.

In addition to the above scenarios, other methods need to be used to ensure atomicity, such as synchronizedor concurrent包.

Let's look at an example of volatile and atomicity:

public class Test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

The above code is relatively simple, that is, create 10 threads, and then perform 1000 i++operations respectively. Under normal circumstances, the output of the program should be 10000, but the results of multiple executions are all less than 10000. This is actually volatilethe reason why atomicity cannot be satisfied.

Why does this happen? It is because although volatile can guarantee incvisibility between multiple threads. But inc++the atomicity cannot.

Summary and reflection

We covered volatilekeywords and synchronizedkeywords. We now know that synchronizedatomicity, ordering, and visibility can be guaranteed. But volatileonly order and visibility can be guaranteed.

So, let's take a look at the singleton implemented by the double-checked lock. It has already been used synchronized, why is it still needed volatile?

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132350560