Do you really understand the volatile keyword in java? Example explanation of volatile principle analysis (easy to understand)

Is i++ thread safe?

Before understanding volatile, let's think about a question, do you think i++ is thread-safe ? why? , with such a thought, we enter the following.

First let's take a look at the memory model.

Memory model:
The Java virtual machine specification attempts to define a Java memory model (JMM) to shield the memory access differences of various hardware and operating systems. JMM stipulates that all variables are stored in main memory, and each thread contains its own All operations in the thread are mainly based on the working memory, they can only access their own working memory, and the value must be synchronized back to the main memory after the operation.
insert image description here

//假设i=1
i=i+1;

According to the above figure, when a thread executes this statement, it first reads the value of i from the main memory, loads it into the working memory, and hands it to the CPU for processing. After the +1 operation, the calculation result is 2, and 2 is written. into working memory, and then write the value into memory.

There is no problem with this code running in a single thread, but running in multiple threads will have problems.

Will the result of executing i++ once in each of the two threads be equal to three?

Assuming i=1, the result of executing i++ twice in a single thread is definitely equal to three, but it is not necessarily in multithreading, because there is thread context switching in multithreading .
let's assume:

  • Thread1 is reading the value i=1 of the main memory and writing it to the working memory 1 , and then performing the +1 operation, and writing i=2 to the working memory 1. If the thread switching occurs at this time, it is switched to thread2 (the value of the main memory is any However, i=1, i=2 in working memory 1 is not written to main memory);
  • Thread2 reads i=1 in main memory, then performs +1 operation, writes i=2 into working memory 2 , then writes 2 into main memory, and the thread switches to thread1
  • thread1 writes i=2 of working memory 1 into main memory.

The i++ happened twice and the result is 2. Now we must understand that i++ may cause thread unsafety.

volatile keyword

Introduction : volatile is a lightweight synchronization mechanism provided by Java. The Java language includes two built-in synchronization mechanisms: and volatile variables, which are more lightweight than synchronized (synchronized is often referred to as a heavyweight lock) because it does not cause thread context switching and scheduling.

Three basic concepts of concurrent programming

  1. Atomicity: That is, an operation or multiple operations are either all executed and the process of execution is not interrupted by any factor, or none of them are executed.
  2. Visibility: When multiple threads access the same variable, one thread modifies the value of the variable, and other threads can immediately see the modified value.
  3. Ordered: That is, the order of execution of the program is executed in the order of the code.

Characteristics of volatile (guaranteed visibility and ordering)

(1) Guaranteed visibility, not atomicity

  1. When a variable is marked volatile, JMM will force the variable in the thread local memory to be flushed to the main memory;

  2. This write will invalidate the cache of volatile variables in other threads.

(2) Prohibition of order rearrangement to ensure orderliness

重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:
  1. A reorder operation does not reorder operations that have data dependencies .

Assuming: a=1;b=a; This instruction sequence, since the second operation depends on the first operation, these two operations will not be reordered at compile time and processor runtime.

  1. Reordering is to optimize performance, but no matter how it is reordered, it is necessary to ensure that the execution result of the program under a single thread cannot be changed

volatile does not guarantee atomicity

Since the i++ operation is not an atomic operation volatile does not guarantee thread safety .

According to the above analysis, i++ can be split into three steps

  1. (read) Read the value of i=1 from main memory to working memory.
  2. (calculation) cpu calculation i=1+1=2;
  3. (write) Write i=2 to working memory, write to main memory.

The three-step operation of i++ is not an atomic operation. Thread switching may occur in each step. Only synchronized or Reentrantlock can be used to ensure atomicity.

The principle of volatile

At the bottom of the JVM, volatile is implemented using "memory barriers". When the volatile keyword is added, the assembly code will have an additional lock prefix instruction, which is actually equivalent to a memory barrier (also a memory fence).

The function of the memory barrier:

  1. It ensures that instructions after it are not queued to the position before the memory barrier during instruction reordering, nor are the previous instructions queued to the back of the memory barrier;
  2. It forces changes to the cache to be written to main memory immediately;
  3. In the case of a write operation, it invalidates the corresponding cache line in other CPUs.

Why add the volatile keyword to DCL (Double Checked Lock Singleton Mode)

public class DoubleCheckLockSingleton {
    
    
   
    private DoubleCheckLockSingleton() {
    
    }

    private static volatile DoubleCheckLockSingleton instance=null;

    public static DoubleCheckLockSingleton getInstance(){
    
    
        if(instance==null){
    
              //标记1                       
           synchronized (DoubleCheckLockSingleton.class){
    
    
               if(instance==null){
    
    
                   instance=new DoubleCheckLockSingleton(); //标记2
               }
           }
        }
        return instance; //标记3
    }

Let's talk about the conclusion first, if you don't add volatile, it may happen that the instance instance is not initialized in extreme cases.

Instruction reordering may occur when executing marker 2 , instance=new DoubleCheckLockSingleton();

object creation process

a. Apply for memory space
b. Assign value to the applied memory, initialize the instance object
c. The address of the instance pointed to by the memory

Prevent instruction reordering

In the above object creation process, instructions may be reordered from abc to acb.

There may be problems with multi-threading

  • When thread 1 executes the code of mark 2 , it is assumed that the new object of thread 1 is reordered, and ac is executed first, but b is not executed. Now the cpu is scheduled and switched to thread 2.
  • Thread 2 executes the code of mark 1 , because instance has already pointed to the memory address, so instance==null is judged to be false (but the instance is not actually initialized at this time).
  • At present, the b operation b of thread 1 is still not executed.
  • Thread 2 will continue to execute the code marked 3 , returning an uninitialized object.
  • There will be a problem with thread 2 continuing to operate on this object.

The addition of the volatile keyword prevents instruction retakes and guarantees execution in the order of abc (this problem exists in theory, but it is unlikely to happen in practice).

Copyright statement : This article is an original article by the blogger and may not be reproduced without the blogger's permission
https://blog.csdn.net/qq_44614710/article/details/120178407

Guess you like

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