Tell me about your understanding of volatile in Java

preface

@H_@ R_ 301_ 2408@_3 @In fact, I have always mastered the knowledge related to volatile, and can roughly tell some knowledge. For example, it can ensure visibility; Command rearrangement is prohibited. These two features will come soon, but we need to go deeper. Specifically, how to implement these two features and what scenario to use @ h_@ R_ 301_ 2408@_3 @Volatile, why not use @ h directly_@ R_ 301_ 2408@_3 @Synchronized is not a good answer to this in-depth and expansion related question. Because @ h_@ R_ 301_ 2408@_3 @Volatile is the knowledge that must be asked in the interview, so I'm going to chew it off this time.

System processing efficiency and Java Memory Model

In the computer, each program instruction is executed in the CPU, and the data of the CPU execution instruction is temporarily stored in the memory, but the execution speed of the CPU is much faster than the reading speed of the memory. If all CPU instructions read data through the memory, the processing efficiency of the system will be greatly reduced, Therefore, modern computer systems have to add one or more layers of cache with reading and writing speed as close as possible to the operation speed of the processor as the buffer between the memory and the processor.

The data needed for the operation is copied to the cache so that the operation can be carried out quickly. When the operation is completed, it is synchronized from the cache back to the memory, so that the processor does not have to wait for slow memory reading and writing.

Although adding cache improves the processing efficiency of CPU, it also brings new problems:

Modern computers are multi-core CPUs. At the beginning, the value of variable a in memory is 1. The first CPU reads the data, and the second CPU also reads the data into its own cache. When the first CPU adds 1 to variable a, the value of variable a becomes 2, and then writes the value of variable a back to memory. At this time, when the second CPU also adds 1 to variable a, Since the value in the cache in the second CPU is still 1, the result after adding 1 is 2, and then the second CPU synchronizes the value of variable a back to memory, resulting in that after two adding 1 operations, the value of variable a is finally 2 instead of 3. This kind of variable accessed by multiple CPUs is usually called shared variable. The above problem is the inconsistency between main memory and cache content after the introduction of cache. Because each processor has its own cache, but they share the same main memory, it is inevitable that the main memory does not know which cache variable should prevail.

The JAVA memory model describes the access rules for various variables and the underlying details of storing and reading variables into and from memory.

The variables concerned in the JAVA memory model are shared variables (instance variables and class variables). All shared variables are stored in main memory, but each thread will keep a copy of the shared variables in its own working memory (processor cache) when accessing the variables.

The Java Memory Model (JMM) stipulates:

All operations (reading and writing) of threads on variables must be performed in the working memory, and the data in the main memory cannot be directly operated. Different threads cannot directly access the variables in each other's working memory, and the variable values between threads must be transferred through the main memory. In JMM, the relationship between working memory and main memory is as follows:

Visibility of volatile (ensure immediate visibility)

Continue with the above cache consistency problem. In the JAVA memory model, this problem is the visibility problem, that is, whether a thread modifies the value of a shared variable is immediately visible to another thread. If it is not immediately visible, there will be a problem of cache consistency. If it is immediately visible, the variable value obtained by another thread during operation is the latest. You can solve the problem of visibility.

So how to solve the visibility problem?

Lock shared variables, whether @ h_@ R_ 301_ 2408@_3 @Synchronized or @ h_@ R_ 301_ 2408@_3 @Lock is OK. The purpose of locking is that only one thread can operate on the shared variable at the same time, that is, during the process of synchronizing the shared variable back to the main memory from reading the working memory to updating the value, other threads cannot operate the variable. This naturally solves the problem of visibility, but it is inefficient. Threads that cannot operate shared variables can only be blocked.

When a shared variable is @ h_@ R_ 301_ 2408@_3 @After volatile modification, it will ensure that each thread will immediately synchronize the modified value of the variable back to the main memory. When other threads need to read the variable, they will read the latest variable value.

So what does volatile do to solve the visibility problem?

被volatile修饰的变量,在被线程操作时,会有这样的机制:

When a thread operates on a variable, it will read the variable from the main memory to its own working memory. When the thread modifies the variable, the variable copy in other threads that have read the variable will become invalid. In this way, when other threads find that the variable has become invalid when using the variable, they will retrieve it from the main memory, In this way, only the latest value is obtained.

So @ h_@ R_ 301_ 2408@_3 @How does the keyword volatile implement this mechanism?

Because a computer has multiple CPUs and the same variable, the cached values in multiple CPUs may be different, so whose cached values prevail?

Since everyone has their own values, there is a protocol between CPUs to ensure that the accurate value of shared variables is determined according to certain laws, so that each CPU operates according to the protocol when reading and writing shared variables.

This is the cache consistency protocol.

The most famous cache consistency protocol is Intel's @ H_@ R_ 301_ 2408@_3 @When talking about MESI, first explain the cache line:

The core idea of MESI is that when the CPU writes a variable and finds that the variable is a shared variable, it will notify other CPUs to set the cache line of the variable to an invalid state. When other CPUs find that the cache line of this variable is invalid when operating variables, they will go to the main memory to re read the latest variables.

Because the communication between the CPU and other components is carried out through the bus, each CPU checks whether its cached value is expired by sniffing the propagation data on the bus. When the processor finds that the memory address corresponding to the line it replaced has been modified, it will set the cache line in its working memory to an invalid state, When the CPU modifies this variable, it will re read the variable from the main memory of the system.

Ordering of volatile (instruction rearrangement prohibited)

Generally speaking, when we write a program, we always write the code from top to bottom. By default, the program is executed from top to bottom. However, in order to improve efficiency, the CPU will reorder the instructions under the condition of ensuring the accuracy of the final result. In other words, the code written before may not be executed first, and the code written after may not be executed late.

for instance:

int a = 5; // 代码1
int b = 8; // 代码2
a = a + 4;	// 代码3
int c = a + b;	// 代码4

The execution order of the above four lines of code may be

When multiple threads execute code, reordering is more prominent. In order to improve their efficiency, each CPU may have competition, which may lead to the correctness of the final execution.

Therefore, in order to ensure the correctness of final execution under multiple threads, the variable is used with @ H_@ R_ 301_ 2408@_3 @Modify volatile to prohibit instruction reordering (in fact, it can also be implemented by locking and other known rules, but we only discuss @ h @ r 301 here_ 2408@_3 @Implementation of volatile).

So @ h_@ R_ 301_ 2408@_3 @How does volatile implement instruction reordering?

The answer is: memory barrier

A memory barrier is a set of CPU instructions used to implement sequential restrictions on memory operations. The java compiler will insert a memory barrier at the appropriate position to prohibit the processor from reordering the instructions when generating the instruction series.

@H_@ R_ 301_ 2408@_3 @Volatile adds two memory barriers before and after variable write operations to ensure that the previous write instructions and subsequent read instructions are orderly.

@H_@ R_ 301_ 2408@_3 @Volatile inserts two instructions after the read operation of a variable, which prohibits the reordering of subsequent read and write instructions.

Therefore, the "happen before" principle has emerged from jdk5, which is also called the "prior occurrence" principle. The principle of antecedent occurrence can be summarized as follows: if the impact of one operation a can be observed by another operation B, it can be said that this operation a occurs in advance of operation B.

The impact here includes the modification of variables in memory, the call of methods, the amount of messages sent, etc.

The antecedent principle in volatile is for a @ H_@ R_ 301_ 2408@_3 @The write operation of volatile variable occurs first in the subsequent read operation of this variable anywhere.

Volatile cannot guarantee atomicity

Atomicity means that an operation process either succeeds or fails. It is an independent and complete process.

As mentioned above, if multiple threads accumulate a variable, they will not get the desired result, because accumulation is not an atomic operation.

To ensure that the final result of accumulation is correct, either lock the accumulation variable or use @ H_@ R_ 301_ 2408@_3 @Variables such as aotomicinteger.

/**
 * 双重检查加锁式单例
 */
public class DoubleCheckLockSingleton implements Serializable{

    /**
     * 静态变量,用来存放实例。
     */
    private volatile static DoubleCheckLockSingleton doubleCheckLockSingleton = null;

    /**
     * 私有化构造方法,禁止外部创建实例。
     */
    private DoubleCheckLockSingleton(){}

    /**
     * 双重检查加锁的方式保证线程安全又能获得到唯一实例
     * @return
     */
    public static DoubleCheckLockSingleton getInstance(){
        //第一次检查实例是否已经存在,不存在则进入代码块
        if(null == doubleCheckLockSingleton){
            synchronized (DoubleCheckLockSingleton.class){
                //第二次检查
                if(null==doubleCheckLockSingleton){
                    doubleCheckLockSingleton = new DoubleCheckLockSingleton();
                }
            }
        }

        return doubleCheckLockSingleton;
    }

}

Why double check? When the first thread goes to the first check and finds that the object is empty, it enters the lock. The second check is also empty, so it creates the object. But at this time, another thread comes to the first check and finds that it is empty, but at this time, because the lock is occupied, it can only block and wait. Then the first thread creates the object successfully, Because the object is modified by volatile and can be fed back to other threads immediately, after the first thread releases the lock, the second thread enters the lock. When the second check is performed, it is found that the object has been created, so the object is not created. So as to ensure the singleton.

There are also steps to create an object:

If the order of these three instructions is rearranged, when multiple threads get the object, it will cause that although the object is instantiated, it does not allocate memory space, and there will be a risk of null pointers. So @ h is added_@ R_ 301_ 2408@_3 @Volatile objects also ensure that they will not be detected as empty by objects already in the creation process during the second inspection.

To sum up

@H_@ R_ 301_ 2408@_3 @Volatile can actually be regarded as a lightweight @ H_@ R_ 301_ 2408@_3 @Synchronized, although @ h_@ R_ 301_ 2408@_3 @Volatile cannot guarantee atomicity, but if the operation under multithreading is an atomic operation (such as assignment operation), the use of volatile will be due to @ h @ r 301_ 2408@_3 @synchronized。

@H_@ R_ 301_ 2408@_3 @Volatile can be applied to the situation that once a flag is modified, it needs to be immediately visible by other threads. You can also modify the variable as a trigger. Once the variable is modified by any thread, it will trigger the execution of an operation.

@H_@ R_ 301_ 2408@_3 @The variable write operation of volatile has before, and any subsequent read operation of this volatile variable.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>