Detailed explanation of Volatile's underlying principle level ---Java multithreading


1. Introduction to Volatile

Volatile can be said to be the most lightweight synchronization mechanism provided by JVM, but it is not easy to be completely understood correctly, and it cannot absolutely guarantee the synchronization of threads, that is to say, under certain conditions, volatile can guarantee threads Synchronization. This also causes many programmers to subconsciously avoid using it. Therefore, choose the more secure synchronized keyword. But Volatile is used in many places, and it is also of great significance to understand many other operations of multithreading.

2. Volatile's role one-visibility

If you understand **JMM (java memory model)**, you will know: if there is no special situation, a thread's reading and writing of shared variables in main memory will be in the thread's own working memory (you can simply understand it as The working memory is equivalent to the cache). So when a thread modifies a shared variable, other threads cannot immediately understand it. After using the volatile keyword to modify the variable, a thread modifies the Volatile variable For other threads, it can be known immediately. It is only the visibility of Volatile. The following is a comparison to show.

public class Volatile
{
    
    
    static boolean flag = true;//定义了一个全局变量
    public static void main(String[] args) throws InterruptedException
    {
    
    
        Thread thread1=new Thread(new Runnable() {
    
    
            @Override
            public void run()
            {
    
    
               while (flag)//当flag为true的时候会一直执行
               {
    
    

               }
            }
        });
        thread1.start();//开启第一个线程

        Thread.sleep(1000);//保证第一个线程一定是比第二个线程先执行
       new Thread(new Runnable() {
    
    
            @Override
            public void run()
            {
    
    
                System.out.println("stop thread1");
                flag=false;//将全局变量设置为false
            }
        }).start();
    }
}

After running this code, you will find that even if the second thread has performed the operation of flag=false;, the first thread will continue to execute. Because the flag in the work area in thread two is still true, it is correct Other threads do not know about the modification of the flag.

But if we declare the flag as Volatile, there will be no such problem.

3. Volatile's role two-prohibit instruction reordering

Instruction reordering means that in a single thread, without affecting the final result, the JVM will execute the code out of order to make full use of system resources.

public class ThreadTest
{
    
    
    static int b,c;
    static int  a;
    private static class ReaderThread extends Thread
    {
    
    
        @Override
        public void run()
        {
    
    
            a=1;  b=2; c=a+b;
        }

    }
}

In this code, it is possible to reorder instructions. The order of operations of a=1 and b=2 may not necessarily be executed in the order of the code, because even if the order of these two operations is reversed, it will not be in a thread. Affect the final result, but c=a+b. This operation must be performed after a=1 and b=2. Because this can ensure that the final result is correct.

Reordering in one thread will not affect the final result. However, if another thread depends on the intermediate results in this thread, errors may occur. Volatile can prohibit instruction reordering through a memory barrier.

public class ThreadTest
{
    
    
    static int b,c;
    volatile static int  a;
    private static class ReaderThread extends Thread
    {
    
    
        @Override
        public void run()
        {
    
    
            a=1;//内存屏障,保证在这之后的代码不会重排序到内存屏障之前
            b=2; c=a+b;
        }
    }
}

4. Volatile principle analysis

If you look at the compiled code that reads and writes to a volatile variable, you will find that there is one more operation compared with ordinary variables: lock addl $0x0, (%esp). This operation is actually equivalent to a memory barrier (Memory Barrier). The lock prefix is ​​the key. The following addl $0x0, (%esp) is a no operation and will not have any effect.

It is the lock operation that guarantees the visibility of volatile and prohibits reordering.

The function of the lcok operation is to write the value of the working memory of this thread into the main memory.This operation has two effects.

  1. It will invalidate the working memory of other threads, that is, other threads' read and write operations on volatile variables must be directly obtained from the main memory. This ensures visibility
  2. Since writing the cache in the working memory to the main memory is equivalent to telling the JVM: I have completed these operations, and the subsequent operations cannot be completed before me, thus creating a memory barrier.

5. Volatile cannot guarantee atomicity.

Atomicity simply means that an operation is indivisible. But volatile does not guarantee that the operation of a variable is atomic. For example: suppose there is a volatile variable a, we perform a++ operation on it. On the surface a++ is an operation But there are three steps:

  1. Read the initial value of a (volatile guarantees that the read must be the latest value)
  2. Calculate a+1
  3. Assign the value of a+1 to a (volatile guarantees that the assigned a will be written to the main memory)

Think about it, if in the second step of calculating a+1, other threads have changed the value of a, then the final result is wrong.

6. Use scenarios of Volatile

Since volatile cannot guarantee atomicity, there are certain restrictions when it is applied to synchronization. In the operation scenario that does not meet the following two rules, we still need to ensure atomicity by other means

  1. The result of the operation does not depend on the current value of the variable or it can be guaranteed that only a single thread can modify the variable
  2. Variables do not need to participate in invariant constraints with other state variables.

There are certain restrictions when synchronizing. In computing scenarios that do not meet the following two rules, we still need to use other means to ensure atomicity

  1. The result of the operation does not depend on the current value of the variable or it can be guaranteed that only a single thread can modify the variable
  2. Variables do not need to participate in invariant constraints with other state variables.

Guess you like

Origin blog.csdn.net/qq_44823898/article/details/110734723