Java Concurrency (3)-Talk about volatile

Source: Internet
Author: User
Tags dateformat modifier volatile

Introduction

When it comes to the volatile keyword, most developers have a certain understanding, which can be said to be a very familiar and very unfamiliar keyword for developers. The equivalent of lightweight synchronized, also known as lightweight locks, is less expensive than synchronized, with visibility, ordering, and partial atomicity, which is a very important keyword in Java concurrency. In this article we will delve into how he guarantees visibility, order, and partial atomicity from the volatile underlying principle, and also summarizes some typical scenarios for volatile keywords.

"Partial" atomicity of volatile

The so-called atomicity, that is, an operation is a complete whole, in other threads it appears that the operation has either not started, or has been completed, do not see the middle of the operation process, a bit similar to the transaction.

So why is it that volatile has only "partial" atomicity, because inherently volatile is not atomic, he modifies only a single variable, and in most cases the reading and assignment of a single variable is inherently atomic, but with one exception, a long in a 32-bit Java Virtual machine /double type variable operation.

In a 32-bit Java Virtual machine, the read and write operation of the long/double variable is divided into two parts: read-write high 32-bit, read-write low 32-bit, or vice versa, so if the variable is not declared as a volatile variable, it is possible to cause unpredictable results when multithreading reads and writes. Since the reading and writing of a single long/double variable is not a whole, that is, there is no atomicity, only the use of volatile modification, the reading and writing of a single long/double type variable has the characteristics of atomicity. Under the 64-bit Java Virtual machine, the long/double variable reads and writes are inherently atomic, and it is not necessary to use the volatile modifier for simple read-write purposes.

It is important to understand that volatile only guarantees that the reading and writing of variables are atomic, and that it is not guaranteed that the compound operation of the variables is atomic, which is the most classic scenario is the self-increment and decrement of a single variable.

private volatile static int increaseI = 0;public static void main(String[] args) {    for (int i = 0; i < 100000; i++) {        Thread thread = new Thread(new Runnable() {                        @Override            public void run() {                                increaseI++;            }        }, String.valueOf(i));        thread.start();    }        while(Thread.activeCount()>1)          Thread.yield();    System.out.println(increaseI);}

If you pass the test, you will find that many times, the printed result is not 100000. This is because volatile modified variables only guarantee that the read and write of the variable is atomic, and increasei++ is a compound operation, he can be simply divided into:

var = increaseI; //步骤1:将increaseI的值加载到寄存器varvar = var + 1;//步骤2:将寄存器var的值增加1increaseI = var;//步骤3:将寄存器var的值写入increaseI

Volatile only guarantees the atomicity of the first and third individual operations, and does not guarantee the atomicity of the entire self-increment and decrement process, that is, the volatile-modified increasei++ is not an atomic operation. This can also be explained in this question:

The visibility of volatile

Regarding the visibility, in the previous "Java Concurrency (2)-chat Happens-before" Article said, in order to improve operational efficiency, read and write shared variables are in the local memory of the thread, when the variable is updated, will not be in time to refresh the results of the variable back to the main memory, in a multithreaded environment , other threads will not be able to read the latest variable values in time. We can analyze this from the code below.

  private static Boolean flag = FALSE;                private static void Refershflag () throws interruptedexception {thread Threada = new Thread (new Runnable () {        @Override public void Run () {while (!flag) {//do something}        }    }); Thread threadb = new Thread (new Runnable () {@Override public void Run () {FL        AG = TRUE;        }    });        DateFormat DateFormat = new SimpleDateFormat ("Yyyy/mm/dd HH:mm:ss");    System.out.println ("Threada start" + Dateformat.format (new Java.util.Date ()));        Threada.start ();        Thread.Sleep (100);        Threadb.start ();    Threada.join (); System.out.println ("Threada End" + Dateformat.format (new Java.util.Date ()));} Threada START2018/07/25 16:48:41  

According to normal logic, the B thread updates the variable flag, the a thread should exit immediately, but in fact many times the B thread does not exit immediately, because the virtual machine takes into account that the shared variable is not a volatile modification, the default variable does not require multi-threaded access, so it is optimized, Causes the flag shared variable not to flush back to main memory in a timely manner, while other threads are not in time to read the results of main memory. What happens when we add a volatile flag to the flags variable?

private volatile static boolean flag = false;//threadA start2018/07/25 16:48:59//threadA end2018/07/25 16:48:59

You can see that the a thread exits immediately, which shows the visibility of the volatile.

The ordering of volatile

JMM on the basis of happens-before rules, it guarantees the order of single thread and correct synchronous multithreading, in which there is a volatile variable rule: The write operation of a volatile variable happen-before the subsequent read operation of the variable.

There are two points to note: 1th, there is a happens-before relationship between the write and read operations of the same volatile variable, and the 2nd, which has a chronological order, must be a write operation Happen-before read operation. In the example of "Java Concurrency (2)-Chat Happens-before" reordering, it is a good indication of the volatile Prohibition reordering feature.

public class Aandb {int x = 0;    int y = 0;    int a = 0;        int b = 0;        public void Awrite () {a = 1;    x = b;        } public void Bwrite () {b = 1;    y = A;        }}public class Athread extends thread{private aandb aandb;    Public Athread (Aandb aandb) {this.aandb = aandb;                } @Override public void Run () {super.run ();    This.aAndB.awrite ();        }}public class Bthread extends thread{private aandb aandb;    Public Bthread (Aandb aandb) {this.aandb = aandb;                } @Override public void Run () {super.run ();    This.aAndB.bwrite ();    }}private static void Testresort () throws interruptedexception {aandb aandb = new Aandb ();        for (int i = 0; i < 10000; i++) {Athread athread = new Athread (AANDB);        Bthread bthread = new Bthread (AANDB);        Athread.start ();        Bthread.start ();        Athread.join ();   Bthread.join ();     if (aandb.x = = 0 && Aandb.y = = 0) {System.out.println ("resort");    } aandb.x = Aandb.y = AANDB.A = aandb.b = 0; } System.out.println ("End");}

When both A and B threads are reordered, they may print out resort, but after the variable is changed to a volatile variable, this condition will not occur again.

Two typical usage scenarios for volatile

1 is used to indicate the amount of state.
The state volume indicator is a Boolean variable that determines whether the logic needs to be executed. Is the code in the above volatile visibility:

Thread threadA = new Thread(new Runnable() {        @Override    public void run() {        while (!flag) {            //do something        }    }});Thread threadB = new Thread(new Runnable() {        @Override    public void run() {                flag = true;    }});

If you use synchronized or lock writing will be more complex, but if using volatile to modify the variable is a good solution to this problem, to ensure that the state of the amount of time to flush back to the main memory and other threads will also be forced to update.

2 Double-check problems
The double-check problem should be the most volatile use scenario. As shown in the following code:

public class DoubleCheck {    private volatile static DoubleCheck instance = null;        private DoubleCheck() {            }        public static DoubleCheck getInstance() {                if (null == instance) {   //步骤一            synchronized (DoubleCheck.class) {                if (null == instance) {   //步骤二                    instance = new DoubleCheck();   //步骤三                }            }        }        return instance;    }        public static void main(String[] args) throws InterruptedException {        DoubleCheck doubleCheck = DoubleCheck.getInstance();    }}

Step three in the code is not atomic, and a bit similar to the previous self-increment, can be divided into three steps:
3.1 Assigning memory addresses to Doublecheck alloc memories address
3.2 Initializing objects Doublecheck init doublecheck
3.3 Referring addresses to instance instance > memory address
In the CPU it appears that 3.2 and 3.3 do not have dependencies and are likely to be reordered if 3.2 and 3.3 are reordered:

Thread 2 at the time of the Step judgment instance is not empty, in fact the object is not initialized, 3.2 is not executed. Causes an error in the next use of the object. Using the volatile modifier instance variable at this point prevents 3.2 and 3.3 reordering, which guarantees the correctness of the code when multithreaded access is available.
We can see that in the assembly code, after using the volatile keyword, the lock instruction in step three is used to guarantee the order of the current execution:
Do not use volatile:

Use volatile

The principle behind volatile

In the assembly code of Doublecheck, we see that after adding the volatile keyword, there is more than one row of lock instructions in the assembly code, so what does this command mean?
The lock command has two functions:

    1. Locks the CPU bus and cache, locks and executes subsequent instructions, and then releases the data in the cache back to main memory when the lock is released.
    2. Lock invalidates cache rows in other CPU caches, and other CPUs must load up-to-date data from main memory when read.
      Simply put, the lock directive can achieve cache consistency. With these two functions of the lock directive, we can easily understand that when the shared variable flag is modified with volatile, the value of each update to the flag causes the data of the cached row to be forced to flush the latest value to the main memory, and the data before the volatile variable is flushed back to the main memory. At the same time, other threads must read the value of the latest flag to main memory. This enables the visibility and ordering of shared variables.


      Resources:
      "In-depth understanding of Java virtual machines"
      The art of Java concurrent programming

Java Concurrency (3)-Talk about volatile

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.