How many people interviewed Volatile? The interview questions are all summed up here

Volatile keywords

volatile It is 轻量级 the synchronization mechanism provided by the Java virtual machine  . What is 轻量级 it for  ? This is relatively  synchronized speaking. Volatile has the following three characteristics.

To figure out 可见性 原子性 指令重排 the meaning of the nouns listed above,  we need to first figure out JMM (what is the Java memory model)

JMM specifies the memory is divided into major  主内存 and  工作内存 two kinds. The main memory and working memory here are divided from JVM memory (heap, stack, method area) at different levels. If you have to correspond, the main memory corresponds to the object instance part in the Java heap, and the working memory Corresponds to some areas in the stack. From a lower level, the main memory corresponds to the physical memory of the hardware, and the working memory corresponds to the registers and cache.

image

The JVM was designed to take into account that if the JAVA thread directly manipulates the main memory every time it reads and writes variables, the performance impact is relatively large, so each thread has its own working memory, and the variables in the working memory are in the main memory One copy  拷贝 , the thread reads and writes variables directly in the working memory, and cannot directly manipulate the variables in the main memory. But there will be a problem in this way. When a thread modifies the variable in its working memory, it is invisible to other threads, which will lead to thread insecurity. Because JMM has developed a set of standards to ensure that developers can control when memory will be synchronized to other threads when writing multi-threaded programs.

The operations of each thread on the shared variables in the main memory are all copied to their own working memory and then written back to the main memory.

It is possible that when a thread A modifies the value of the shared variable X but has not written back to the main memory, another thread B aligns the same shared variable X in the memory to operate, but at this time the shared variable in the working memory of the A thread X is not visible to thread B. This delay in synchronization between the working memory and the main memory causes visibility problems.

Look at the problem of visibility through code

package com.dpb.spring.aop.demo;

import java.util.concurrent.TimeUnit;

/**
 * 可见性问题分析
 */
public class VolatileDemo1 {
    public static void main(String[] args){
        final MyData myData = new MyData();
        // 开启一个新的线程
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "开始了...");
            try{TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
            // 在子线程中修改了变量的信息  修改的本线程在工作内存中的数据
            myData.addTo60();
            System.out.println(Thread.currentThread().getName() + "更新后的数据是:"+myData.number);
        },"BBB").start();
        // 因为在其他线程中修改的信息主线程的工作内存中的数据并没有改变所以此时number还是为0
        while(myData.number == 0){
            // 会一直卡在此处
            //System.out.println("1111");
        }
        System.out.println(Thread.currentThread().getName()+"\t number =  " + myData.number);
    }
}

class MyData{
	// 没有用volatile来修饰
    int number = 0;

    public void addTo60(){
        this.number = 60;
    }

}

The effect is as follows:

image

By  volatile solving this problem to

image

image

We can find that when the variable is  volatile modified, the corresponding variable in other threads can be known immediately after the variable in the working memory of the child thread is modified. This is what we are talking about visibility

The atomicity is  不可分割 ,  完整性 that is, when a thread is doing a specific business, it cannot be blocked or divided in the middle, and needs to be completed as a whole, either at the same time or fail at the same time.

Volatile is  不支持 atomic, and we can verify it next.


import java.util.concurrent.TimeUnit;

/**
 * 可见性问题分析
 */
public class VolatileDemo2 {
    public static void main(String[] args){
        final MyData2 myData = new MyData2();
        for (int i = 1; i <= 20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    myData.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        // 等待子线程执行完成
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        // 在主线程中获取统计的信息值
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.number);
    }
}

class MyData2{
   // 操作的变量被volatile修饰了
    volatile int number = 0;

    public void addPlusPlus(){
        number++;
    }

}

Effect of execution

image

According to the normal logic, the 20 sub-threads are opened, and the result should be 20000 after each execution 1000 times. However, we found that the result of the operation is likely to be smaller than we expected, and the variables have also been modified by volatile. It shows that the atomicity that does not meet our requirements. In this case, we have to ensure the atomicity of the operation, we have two choices

  1. Realized by synchronized

  2.  Realize through the  JUC following AtomicInteger

The realization of synchronized is heavyweight, affecting the efficiency of concurrency, so we achieve it through AtomicInteger.

package com.dpb.spring.aop.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 可见性问题分析
 */
public class VolatileDemo2 {
    public static void main(String[] args){
        final MyData2 myData = new MyData2();
        for (int i = 1; i <= 20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    myData.addPlusPlus();
                    myData.addAtomicPlus();
                }
            },String.valueOf(i)).start();
        }
        // 等待子线程执行完成
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        // 在主线程中获取统计的信息值
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.atomicInteger.get());
    }
}

class MyData2{
   // 操作的变量被volatile修饰了
    volatile int number = 0;
    // AtomicInteger 来保证操作的原子性
    AtomicInteger atomicInteger = new AtomicInteger();

    public  void addPlusPlus(){
        number++;
    }

    public void addAtomicPlus(){
        atomicInteger.getAndIncrement();
    }

}

effect:

image

注意 : Through the effect, it is found that  AtomicInteger the data processed in the multi-threaded environment is consistent with the result we expect  20000 . It shows the atomicity of the realized operation.

Orderliness

When a computer is executing a program, in order to improve performance, compilers and processors often rearrange instructions, generally divided into the following three types:

image

1. Ensure that the final execution result of the program is consistent with the result of the sequential execution of the code in the single-threaded environment.

2. The processor must consider the data dependency between instructions when reordering.

3. In a multithreaded environment, threads are executed alternately. Due to the existence of compiler optimization rearrangement, it is impossible to determine whether the variables used in the two threads can guarantee consistency, and the result is unpredictable.

Case code

package com.dpb.spring.aop.demo;

public class SortDemo {
    int a = 0;
    boolean flag = false;

    public void fun1(){
        a = 1;  // 语句1
        flag = true; // 语句2
    }

    public void fun2(){
        if(flag){
            a = a + 5; // 语句3
            System.out.println("a = " + a );
        }
    }
}

注意: In a multithreaded environment, threads are executed alternately. Due to the existence of compiler optimization rearrangement, it is impossible to determine whether the variables used in the two threads can guarantee consistency, and the result is unpredictable.

Summary of instruction rearrangement:

Volatile implementation prohibits instruction rearrangement optimization, so as to avoid out-of-order execution of programs in a multithreaded environment.

First understand a concept,  内存屏障 also known as  内存栅栏 a CPU instruction, which has two functions:

  1. Is to ensure the order of execution of specific operations

  2. Is to ensure the memory visibility of certain variables (using this feature to achieve volatile memory visibility)

Because both the compiler and the processor can perform instruction rearrangement optimization. If a Memory Barrier is inserted between instructions, it tells the compiler and CPU that no instructions can be reordered with this Memory Barrier instruction, that is to say, by inserting a memory barrier, the instructions before and after the memory barrier are prohibited from performing reordering optimization. Another function of the memory barrier is to force the cache data of various CPUs to be flushed out, so any thread on the CPU can read the latest version of these data.

Summary of thread safety:

  1. The synchronization delay between the working memory and the main memory  可见性问题 can be solved by using the synchronized or volatile keywords. Both of them can make the modified variables of one thread visible to other threads immediately.

  2. The  volatile keyword can be used to solve the 可见性问题 sum  caused by instruction rearrangement  有序性问题, because another function of volatile is to prohibit reordering optimization.

Guess you like

Origin blog.csdn.net/Lubanjava/article/details/106266276