In depth analysis of the use of the volatile keyword in Java

  • 2020-04-01 02:16:52
  • OfStack

There is a major confusion about the use of the keyword volatile in concurrent processing of Java threads, which is used to think that all is well in concurrent processing of multiple threads.

The Java language supports multithreading. To solve the problem of concurrent threads, synchronized blocks and volatile keywords are introduced in the language.

synchronized
Synchronized blocks are familiar to all, and are implemented by the synchronized keyword, all plus synchronized and block statements, and only one thread at a time can use synchronized to modify a method or block of code during multithreaded access.

volatile
Volatile modifies a variable so that the thread reads the most modified value of the variable each time it USES it. Volatile can be easily misused for atomic operations.

Let's look at an example where we implement a counter that is incremented by a call to the counter inc method each time a thread starts

Execution environment -- JDK version: JDK 1.6.0_31, memory: 3G    CPU: 2.4 G x86


public class Counter { 

    public static int count = 0; 

    public static void inc() { 

        //Here the delay of 1 millisecond makes the result obvious
        try { 
            Thread.sleep(1); 
        } catch (InterruptedException e) { 
        } 

        count++; 
    } 

    public static void main(String[] args) { 

        //Start 1000 threads at the same time to do the i++ calculation and see the actual result

        for (int i = 0; i < 1000; i++) { 
            new Thread(new Runnable() { 
                @Override
                public void run() { 
                    Counter.inc(); 
                } 
            }).start(); 
        } 

        //This might be different every time I run it, maybe 1000
        System.out.println(" The results :Counter.count=" + Counter.count); 
    } 
}

Run Counter. Count =995
The actual result may not be the same each time, the result of the native is: run the result :Counter. Count =995, you can see that in the multi-threaded environment, Counter. Count does not expect the result to be 1000

Many people think that this is a multithreaded concurrency problem and that you can avoid it by adding volatile before the variable count, so let's change the code and see if the result matches our expectations

public class Counter { 

    public volatile static int count = 0; 

    public static void inc() { 

        //Here the delay of 1 millisecond makes the result obvious
        try { 
            Thread.sleep(1); 
        } catch (InterruptedException e) { 
        } 

        count++; 
    } 

    public static void main(String[] args) { 

        //Start 1000 threads at the same time to do the i++ calculation and see the actual result

        for (int i = 0; i < 1000; i++) { 
            new Thread(new Runnable() { 
                @Override
                public void run() { 
                    Counter.inc(); 
                } 
            }).start(); 
        } 

        //This might be different every time I run it, maybe 1000
        System.out.println(" The results :Counter.count=" + Counter.count); 
    } 
}

The result :Counter. Count =992

The result is still not 1000 as we expected, so let's analyze the reasons

In the Java garbage collection article, the allocation of memory at runtime for the JVM was described. One of the memory areas is the JVM virtual machine stack, where each thread has a thread stack when it runs, and the thread stack holds information about the values of variables at the thread run time. When threads access a certain object value, first by object reference to find corresponding value of the variable in the heap memory, and then put the specific value of the heap memory load to the thread in the local memory, build a copy of the variable, then a thread will no longer have anything to do with objects in the heap memory variable values, but copy directly modify the value of a variable, the change after a certain time (thread before exit), automatic to thread a copy of the variable value back to write objects in the heap variables. This changes the value of the object in the heap. Here's a picture

Describe this write interaction

The < img style = "border = 0 border - BOTTOM: 0 px; BORDER - LEFT: 0 px; DISPLAY: inline. BORDER - TOP: 0 px; BORDER - RIGHT: 0 px "title =" Java volatile1 "BORDER = 0 Alt =" Java volatile1 "SRC =" / / img.jbzj.com/file_images/article/201309/20130906172633242.jpg "width = 579 height = 555 data - pinit =" registered ">

Read and load copies variables from main memory into the current working memory
The use and assign  Execute the code to change the value of the Shared variable
Store and write flush main memory with working memory data

Use and assign can appear multiple times

However, these operations are not atomic, that is, after the read load, if the main memory count variable is modified, the value in the thread working memory will not change because it has been loaded, so the calculated result will be different from the expected one

For variables decorated with volatile, the JVM virtual machine simply ensures that the values loaded from main memory into the thread's working memory are up to date

For example, if thread 1 and thread 2 find that the value of count in main memory is 5 in the read load operation, then the latest value will be loaded

After thread 1 heap count is modified, it is written into main memory, and the count variable in main memory becomes 6

Since thread 2 has performed the read,load operation, after the operation, it will also update the variable value of main memory count to 6

Even after the two threads have been modified with the volatile keyword in a timely manner, there will still be concurrency.


Related articles: