[Java Basics] volatile keyword

About the author: CSDN content partner and technical expert, who has worked on daily activities with tens of millions of APPs from scratch.
Focus on sharing original series of articles in various fields, good at java backend, mobile development, artificial intelligence, etc. I hope you will support me a lot.

1. Introduction

We continue to summarize and learn the basics of Java, reviewing the past and learning the new.

2. Overview

Volatile is a Java keyword that can be used to modify variables. Volatile is also known as lightweight synchronized, and the runtime overhead is smaller than synchronized.

2.1 Function

1. Ensure the synchronization of shared variables between threads to achieve visibility.
2. Disable processor reordering.

2.2 Access process of multi-threaded shared variables

When a thread executes, first copy the main memory data to the local thread, and then flush the result from the thread local to the main memory after the operation is completed.
Let's look at the figure below. When multi-threading, the value of the shared variable operation is in the red area.
insert image description here

2.3 Why does multi-threading have visibility problems

Visibility is caused by the CPU cache. The CPU increases the cache to balance the speed difference with the memory, resulting in visibility problems.
In a multi-threaded environment, multiple threads access shared variables at the same time. Due to the uncertain execution order and time of different threads, the modification of shared variables by one thread may not be visible in other threads. Refer to the access process of shared variables above.

2.4 How does volatile achieve visibility

Volatile does not allow caching and reordering within threads, and directly modifies memory, so it is visible to other threads.

When a variable modified by volatile is read or written, it will be directly flushed to the main memory, thus making the variable visible.

2.5 How to realize the prohibition of instruction reordering

Volatile is to prohibit instruction reordering by adding a "memory barrier" to the instruction sequence when the compiler generates bytecode.

When a variable is modified, it means that the variable is "volatile" or "unstable", which means that the value of the variable may be modified by other threads (or processes).

Note: The volatile keyword can only guarantee synchronization between threads, not thread safety .
To ensure thread safety, you need to use other synchronization mechanisms, such as the synchronized keyword or the Lock interface.

Volatile is visible and orderly , but not atomic, so it is not thread-safe.

2.6 Examples

  • Do not use the volatile keyword
禁止线程缓存变量结果。
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。
引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。
举例:
// Thread-A
new Thread("Thread A") {
    
    
    @Override
    public void run() {
    
    
        while (!stop) {
    
    
        }
        System.out.println(Thread.currentThread() + " stopped");
    }
}.start();

一个线程内使用了停止的开关,假如这个stop没有被volatile修饰,我们在线程b中修改,
线程a并不知道开关的值被修改了。
  • Use volatile to prevent reordering
从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现
public class Singleton {
    
    
public static volatile Singleton singleton;
    /**
    * 构造函数私有,禁止外部实例化
    */
    private Singleton() {
    
    };
    public static Singleton getInstance() {
    
    
        if (singleton == null) {
    
    
            synchronized (singleton.class) {
    
    
                if (singleton == null) {
    
    
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

实例化一个对象其实可以分为三个步骤:
* 分配内存空间。
* 初始化对象。
* 将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
* 分配内存空间。
* 将内存空间的地址赋值给对应的引用。
* 初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。
因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
  • Use volatile to ensure atomicity: single read/write
volatile变量的单次读/写操作可以保证原子性的,如longdouble类型变量,
但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作,要保证多步的原子性,
可以通过AtomicInteger或者Synchronized来实现,本质上就是cas操作。

2.7 to answer questions

  • Question 1: Why can't i++ guarantee atomicity?
i++其实是一个复合操作,包括三步骤:
* 读取i的值。
* 对i加1* 将i的值写回内存。 
volatile是无法保证这三个操作是具有原子性的,
我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。 
  • Question 2: Why use volatile for shared long and double variables?
因为longdouble两种数据类型的操作可分为高32位和低32位两部分,
因此普通的longdouble类型读/写可能不是原子的。
因此,鼓励大家将共享的longdouble变量设置为volatile类型,
这样能保证任何情况下对longdouble的单次读/写操作都具有原子性

3. Principle

At the bottom of the JVM, volatile is implemented using a "memory barrier". When the volatile keyword is added, there will be an extra lock prefix instruction.
Memory barrier, also known as memory barrier, is a CPU instruction.

1. Use the javac command to compile and generate a .class file.
2. Use the javap command to decompile and view the information of the .class file, and you can see that there are some more instructions in the bytecode information.

In order to ensure that the caches of each processor are consistent, the cache consistency protocol (MESI) is implemented. Each processor checks whether the value of its cache is expired by sniffing the data propagated on the bus. When the processor finds itself If the memory address corresponding to the cache line is modified, the cache line of the current processor will be set to an invalid state. When the processor modifies the data, it will re-read the data from the system memory into the processor cache.

The volatile variable enables each thread to obtain the latest value of the variable through such a mechanism.

3.2 Usage scenarios:

Solve the requirement for variable visibility, but not for the reading order.

  • Volatile features:
    (1) volatile can only be used at the variable level
    (2) volatile can only achieve variable modification visibility, and cannot guarantee atomicity. volatile + cas achieves atomicity, such as the classes under the atomic package.
    (3) volatile will not cause thread blocking
    (4) Variables marked with volatile will not be optimized by the compiler

4. Recommended reading

[Java Basics] Atomicity, Visibility, Orderliness

[Java Basics] Happens-before of java visibility

[Java Basics] java-android interview Synchronized

[Java Basics] java-android interview - thread status

[Java Basics] Thread related

[Java Basics] java exception

[Java Basics] java reflection

[Java Basics] java generics

[Java Basics] java annotations

[Java Basics] java dynamic proxy

[Java Basics] Java SPI

[Java Basics] Java SPI II Java APT

[Java Basics] jvm heap, stack, method area & java memory model

Guess you like

Origin blog.csdn.net/fumeidonga/article/details/130353475