Recently in the reorganization of multithreading, synchronization related knowledge points. Read a lot of blog articles about the keyword volatile, found that the quality is not suitable for xiaobai, finally found a very easy to understand English. Therefore, in the process of learning, TRANSLATION comes down easily, on the one hand to consolidate knowledge, on the other hand, I hope to help partners in need. Java Volatile Keyword Java Volatile Keyword

Basic usage

The Volatile keyword in the JAVA language is used to modify variables as follows. Representation: This variable needs to be stored directly in main memory.

public class SharedClass {
    public volatile int counter = 0;
}
Copy the code

Int Counter variables decorated with the volatile keyword are stored directly into main memory. And all reads of this variable are read directly from main memory, not from the CPU cache. (Don’t worry if you don’t understand the difference between main memory and CPU cache, it will be explained below.)

What problem does this solve? There are two main problems:

  • Multithreading sees visibility problems,
  • CPU instruction reorder problem

Note: For descriptive purposes, we will refer to volatile variables as “volatile variables” for short, and to variables that are not volatile as “non-volatile”.

Understand the volatile keyword

Variable Visibility Problem

Volatile ensures that variable changes are visible across multiple threads. In a multithreaded application, each thread copies the variable from main memory to the cache of the thread’s CPU by default for computational performance purposes, and then reads and writes the variable. Today’s computers are mostly multi-core cpus, and different threads may run on different cores, each with its own cache space. As shown in the figure below (CPU 1 and CPU 2 in the figure can be directly understood as two cores) :

One problem is that the JVM does not guarantee when data from the CPU cache is written to main memory, nor when data is read from main memory to the CPU cache. In other words, threads on different cpus may read different values for the same variable, which is commonly referred to as an invisible problem between threads. For example, Thread 1’s change of counter = 7 is only visible in CPU 1’s cache. When Thread 2 reads the variable counter from CPU 2’s cache, the value of the variable counter is still 0.

One purpose of volatile was to address interthread invisibility. Variables that were volatile would become visible across threads. The solution is mentioned at the beginning of the article:

All reads to volatile variables are read directly from main memory, rather than from the CPU’s cache. All writes to that variable are written to main memory.

Since the main memory is shared by all cpus, it stands to reason that even threads on different cpus can see changes made to this variable by other threads.

Volatile does more than just ensure visibility of volatile variables

Volatile actually does more for visibility than making volatile variables visible:

  • When Thread A modifies A volatile variable V, Thread B immediately reads it. Once Thread B reads variable V, not only is variable V visible to Thread B, but all variables visible to Thread A before Thread A modifies variable V are visible to Thread B.
  • When Thread A reads A volatile variable V, all other variables visible to Thread A are also read from main memory.

Here’s an example:

public class MyClass {
    private int years;
    private int months
    private volatile int days;


    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days = days; }}Copy the code

The MyClass class has an update method that updates all three variables of the class: years, months, and days. Only days is volatile. When this.days = days is executed, that is, when changes to the days variable are written to main memory, all other variables visible to the Thread, years, months, are also written to main memory. In other words, when days are modified, changes to years and months are visible to other threads.

Here’s another example of reading:

public class MyClass {
    private int years;
    private int months
    private volatile int days;

    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }

    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days = days; }}Copy the code

If we call totalDays() of the same MyClass object with another Thread, the line int total = this.days is volatile. Days will be read from main memory, and months and years will be read from main memory for all other variables visible to the Thread. In other words, the thread gets the latest days, months, and years values.

That’s all about volatile solving the visibility problem.

Instruction reorder challenge

For computational performance, the JVM and CPU allow reordering of instructions within a program in semantically consistent scope classes. Here’s an example:

int a = 1;
int b = 2;

a++;
b++;
Copy the code

This code, after being reordered, might become:

int a = 1;
a++;

int b = 2;
b++;
Copy the code

Int a = 1; int b = 2; int b = 2; At first glance, this reordering looks fine, but if we make one of the variables volatile, and we combine that with the visibility extension problem, you might get a clue. Again, take the MyClass class in the visibility problem:

public class MyClass {
    private int years;
    private int months
    private volatile int days;


    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days = days; }}Copy the code

In the visibility section we said that in the update() method, changes to years and months are also written to main memory when the days line is changed. But what happens if the JVM reorders instructions at this point? Suppose the reordered update() is executed as follows:

public void update(int years, int months, int days){
    this.days   = days;
    this.months = months;
    this.years  = years;
}
Copy the code

In other words, the changes made to months and years are not guaranteed to be visible to other threads. Does that nullify the extension of volatile’s visibility guarantee? We don’t encounter this problem with volatile in practice because JAVA already has a solution: the happens-before rule.

Java volatile happens-before rule

In the face of instruction reordering adjustments to visibility, volatile uses the happens-before rule:

  • Any variable writes that precede volatile writes in the original execution order may not, after reordering, be placed after volatile writes.
  • All other variable reads and writes that should have occurred Before volatile variables were written must still happen Before them.
  • Any variable reads that follow volatile reads in the original execution order may not, after reordering, be placed before volatile reads.

With the two happens-before rules above, we avoid the impact of instruction reordering on volatile visibility.

Volatile does not guarantee atomicity

We often refer to the “three qualities” in multi-threaded concurrency: visibility, order, and atomicity. While volatile guarantees visibility and order, it does not guarantee atomicity.

When Thread 1 and Thread 2 simultaneously modify the volatile counter on the unified object, for example, counter++ is executed simultaneously. At this point, the value of counter read by both threads may be 0. After calculation by each thread, they think that counter + 1 will result in 1. In the end, although we did + 1 on the counter variable with two separate threads, we ended up with 1 instead of 2. Therefore, we say that volatile does not guarantee atomicity for reads and writes to the variable.

To avoid this problem, use the synchronized keyword. Use the synchronized keyword to modify our method/code block of the variable read/write operation (counter++) to ensure atomicity of the read/write operation.

In addition to the synchronized keyword, we can define counter variables directly using only the AtomicInterger type. AtomicInteger provides atomic operations on Integer. Similar classes are AtomicBoolean and AtomicLong. Both synchronized and AtomicXXX classes can guarantee atomicity. The former is based on the principle of locking (pessimistic locking), while the latter is based on the CAS principle (optimistic locking).

In what scenario is volatile sufficient? For example, when a variable is being modified by only one thread and only reads are being performed by other parallel threads, volatile is sufficient.

Performance issues with Volatile

If you are familiar with the CPU’s multi-level caching mechanism, you can probably guess that reading data from main memory is much less efficient than reading data from the CPU’s cache. The purpose of including instruction reordering is also to improve computational efficiency. When the reordering mechanism is limited, computational efficiency will also be affected accordingly. Therefore, we should use the volatile keyword only when we need to ensure visibility and order of variables.