Do you really understand the volatile keywords?

Today, let us work together to explore the knowledge of concurrent programming in Java: volatile keyword

This article explains the volatile keyword from the following three points:

  1. What keywords are volatile?
  2. The volatile keyword can solve any problem? What use scenes?
  3. Realization of the principle volatile keyword?

What keywords are volatile?

Sun's JDK in the official document is to describe the volatile:

The Java programming language provides a secondmechanism, volatile fields, that is more convenient than locking for somepurposes. A field may be declared volatile, in which case the Java Memory Modelensures that all threads see a consistent value for the variable.

That is, if a variable is added to the volatile keyword tells the compiler and JVM memory model: This variable is shared by all threads, visible, each JVM reads the value of the latest written and make it the latest CPU values ​​in all visible. volatile can guarantee the visibility of the threads and provides a certain orderliness, but can not guarantee atomicity. In underlying JVM volatile memory barrier is employed to achieve.

Through this, we can know that there are two volatile characteristics:

  1. Ensure visibility, does not guarantee atomicity
  2. Prohibit instruction reordering

Atoms and visibility

Atomicity refers to one or more operations or all operations performed and the process will not be interrupted execution of any factor, or not implemented. Property and transaction database as a set of actions either succeed or fail. See the following example to understand a few simple atomic:

i == 0;       //1
j = i;        //2
i++;          //3
i = j + 1;    //4

Before looking at the answer, you can think about the above four operations, which is an atomic operation? What is non-atomic operation?

The answer is revealed:

1——是:在Java中,对基本数据类型的变量赋值操作都是原子性操作(Java 有八大基本数据类型,分别是byte,short,int,long,char,float,double,boolean)
2——不是:包含两个动作:读取 i 值,将 i 值赋值给 j
3——不是:包含了三个动作:读取 i 值,i+1,将 i+1 结果赋值给 i
4——不是:包含了三个动作:读取 j 值,j+1,将 j+1 结果赋值给 i

That is, only a simple reading assignment (digital and must be assigned to a variable, each assignment is not an atomic operation between variables) is an atomic operation.

NOTE: Due to the previous operating system is 32-bit, 64-bit data (long type, double-type) is 8 bytes, a total of 64 bits wide, it is necessary to complete the assignment into two operations using a variable or read in Java fetch operations. With the 64-bit operating systems become more popular, the HotSpot JVM implemented in 64-bit, 64-bit data (long type, double type) do atoms treatment (due to the JVM specification does not specify, or JVM implementation does not exclude other treated to 32-bit mode).

In a single-threaded environment, we believe these steps are atomic operations, but in a multithreaded environment, Java can only guarantee the above-mentioned basic data types assignment is atomic, other operational errors may occur in the course of operation . For this purpose, multi-threaded environment in order to ensure atomicity of some operations such as the introduction of locks and synchronized keyword.

Speaking above the volatile keyword to ensure the visibility of the variables, it does not guarantee atomicity. Atomic has been said, below said lower visibility.

In fact, the visibility and set about the Java Memory Model: Java memory model specifies that all variables are exist among the main memory (thread shared area), each thread has its own working memory (private memory). All operations threads of the variables must be in working memory, rather than directly to the main memory to operate. And each thread can not access the working memory of other threads.

Here is a simple chestnut:

For example, the above operation i ++, in Java, execute i++the statement:

First, a thread of execution in a i read from the main memory (raw value) into the working memory, and then performs arithmetic +1 in the working memory (main memory unchanged value of i), the calculation result finally flushed to main memory.

Data operation is carried out in private memory executing thread, the thread after executing the operation, the operation will not necessarily result immediately flushed to main memory (although the last will update main memory), the action is flushed to main memory by the CPU choose an appropriate time-triggered. Before assuming that the value is not updated to the main memory, when the other threads to read (and read the priority of data, rather than main memory working memory), this time may still be the original value of the old main memory, it may lead to result of the operation error.

The following code is the test code:

package com.wupx.test;

/**
 * @author wupx
 * @date 2019/10/31
 */
public class VolatileTest {

    private boolean flag = false;

    class ThreadOne implements Runnable {
        @Override
        public void run() {
            while (!flag) {
                System.out.println("执行操作");
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("任务停止");
        }
    }

    class ThreadTwo implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(2000L);
                System.out.println("flag 状态改变");
                flag = true;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        VolatileTest testVolatile = new VolatileTest();
        Thread thread1 = new Thread(testVolatile.new ThreadOne());
        Thread thread2 = new Thread(testVolatile.new ThreadTwo());
        thread1.start();
        thread2.start();
    }
}

These results likely after thread 2 complete execution flag = true, does not guarantee that the thread 1 while able to immediately stop the cycle, because the flag state is, above all in the private memory thread 2, and flushed to main memory timing is not fixed, and thread 1 read flag value is in its own private memory, the memory flag and private thread 1 still false, so it may cause the thread will continue while loop. Results are as follows:

执行操作
执行操作
执行操作
flag 状态改变
任务停止

Avoid these unpredictable problems occur with the volatile keyword is modified flag, volatile modification of shared variables can be modified to ensure that value is updated immediately after the operation to the main memory inside, when there are other threads need to operate the variable, not from the private memory read, but force a new value is read from main memory. That one thread modifies the value of a variable, this new value to other thread is immediately visible.

Instruction reordering

Generally, a processor in order to improve process efficiency, might enter the code optimization, it does not ensure the implementation of the program in individual statements in the order consistent with the order of the code, but it will ensure the implementation of the program and the final results of the code execution order the result is the same.

For example, the following code

int i = 0;             
boolean flag = false;
i = 1;        // 1
flag = true;  // 2

Int code defines a variable, defines a boolean variable, then the variables are two assignment. The code sequence from the point of view, the statement is a statement in front of 2, then the JVM when you actually execute this code will ensure that a statement will be executed in the preceding sentence 2 do? Not necessarily, why? There may be instructed to reorder (InstructionReorder) occurs.

Sentence 1 and sentence 2 who should perform the final result did not affect the program, it would be possible in the implementation process, the statement 2 statement is executed after the first execution 1.

Note, however, although the processor will reorder instructions, but it will ensure that the program will be the final result and order of the code to perform the same result, then rely on it to ensure it? A look at the following example:

int a = 10;     // 1
int r = 2;      // 2
a = a + 3;      // 3
r = a * a;      // 4

This code sequence may be executed 1-> 2-> 3-> 4 or 2-> 1-> 3-> 4, 3 and 4 but performed in the order will not change, because the processor performing re when sorting will consider data dependencies between instructions, must be used if an instruction Instruction2 Instruction1 result, the processor will be performed prior to ensure Instruction1 Instruction2.

While the reordering does not affect the result of a single thread of execution, but multi-threading it? We see an example below:

// 线程1
String config = initConfig();    // 1
boolean inited = true;           // 2
 
// 线程2
while(!inited){
       sleep();
}
doSomeThingWithConfig(config);

In the above code, since the statements statements 1 and 2 there is no data dependency, and therefore may be reordered. If the reordering occurs, during the execution of the first thread 1 executes the statement 2, but this time the thread 2 would have thought initialization has been completed, it will jump out of the while loop, to perform doSomeThingWithConfig (config) method, but this time did not config is initialized, it will cause an error.

As can be seen from the above, the instruction does not affect reordering of the execution of a single thread, but will affect the validity of concurrently executing threads.

Then the volatile keyword modified variable prohibit reordering means:

  1. When the program executes a read operation or write operation volatile variables, its operation must have all of the foregoing, and the operation on the back is visible behind it in operation certainly not be
  2. After the instruction during optimization, can not be placed before the volatile variable statement variables volatile read and write operations, nor can the latter into its volatile variables statements executed on the front

For chestnut:

x=0;             // 1
y=1;             // 2
volatile z = 2;  // 3
x=4;             // 4
y=5;             // 5

When volatile variable z is variable, instruction reordering, the statement does not put 3 statement 1, 2 before the statement, nor will statement 4, 5 statements into statements back 3. However, the statement Statement 1 and 2, the sequence of statements between 4 and 5 do not make any statements guaranteed and guaranteed volatile keyword, 3 to execute the statement, the statement Statement 1 and 2 must be finished completely, and the statement 1 2 and the results of a statement sentence is 3, 4 statement, the statement 5 is visible.

Back to the previous example:

// 线程1
String config = initConfig();   // 1
volatile boolean inited = true; // 2
 
// 线程2
while(!inited){
       sleep();
}
 
doSomeThingWithConfig(config);

He said before the example mentioned possible sentence 2 will be executed before the statement 1, then it may lead to the execution doSomThingWithConfig () method will result in an error when.

Here inited be modified if the volatile keyword variables, you can ensure that the statement is executed at 2:00, will be able to ensure the config has been initialized.

volatile scenarios

synchronized keyword is to prevent multiple threads to execute a piece of code, it will very much influence the efficiency of program execution, and the volatile keyword in some cases better performance than synchronized, but be aware that the volatile keyword is no substitute for the synchronized keyword because the volatile keyword can not guarantee atomic operations. In general, the use of volatile must meet the following three conditions:

  1. Writes to variables do not depend on the current value of the variable, or to ensure that only a single thread to update the value of the variable
  2. This variable will not be included invariance conditions along with other state variables
  3. You do not need to lock when accessing the variable

The above three conditions need only guaranteed to be atomic operations, in order to ensure that the use of the volatile keyword program can be executed properly at high concurrency. It is not recommended for use in getAndOperate volatile situation, only set or get the scenario is suitable volatile.

Two common scenarios are:

  1. State the amount of label
volatile boolean flag = false;

while (!flag) {
    doSomething();
}

public void setFlag () {
    flag = true;
}

volatile boolean inited = false;
// 线程 1
context = loadContext();
inited = true;

// 线程 2
while (!inited) {
    sleep();
}
doSomethingwithconfig(context);
  1. DCL double checking lock - Singleton
public class Singleton {
    private volatile static Singleton instance = null;

    private Singleton() {
    }

    /**
     * 当第一次调用getInstance()方法时,instance为空,同步操作,保证多线程实例唯一
     * 当第一次后调用getInstance()方法时,instance不为空,不进入同步代码块,减少了不必要的同步
     */
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Recommended reading: design pattern - singleton

When the reasons explained above, the use of volatile reordering already talked about. Mainly instance = new Singleton (), which is not an atomic operation, the JVM words do three things:

  1. For instance allocate memory
  2. Call to Singleton's constructor to initialize member variables
  3. The instance object points to allocate memory storage space (executes this step instance on a non-null)

But there is optimized instruction reordering JVM-time compiler, that is above the second and third steps order is not guaranteed, the final execution order may be 1-2-3, it may be 1-3- 2. If the latter, after the thread 1 before executing the 3, 2, 2 preempted by the thread, has a non-null time instance (but not initialized), the thread 2 will be reported using the instance returned null pointer exception.

volatile characteristic is how to achieve it?

Front covers some use on the volatile keyword, let's explore volatile in the end how to ensure visibility and prohibit instruction reordering.

In the "in-depth understanding of the Java Virtual Machine," the book says:

Observation was added and no added volatile keyword volatile keyword generated assembly code found is added the volatile keyword, a lock will be more prefix instructions.

For the next chestnuts:

The volatile Integer increment (i ++), in fact, to be divided into three steps:

  1. Volatile read variable value to local
  2. Increase the value of the variable
  3. The value of the local write back and let other threads visible

This JVM instruction 3 steps:

mov    0xc(%r10),%r8d ; Load
inc    %r8d           ; Increment
mov    %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier

lock prefix instruction effectively a memory barrier (also called memory fence), a memory barrier will provide three functions:

  1. It ensures that the following instruction will not discharged to a position before the memory barrier instruction reordering, nor will later discharged preceding instruction memory barrier; i.e., when executing the memory barrier command phrase, in front of it operation has been completed (that bans the reordering)
  2. It will be forced to modify the operation immediately written to the cache main memory (satisfying visibility)
  3. If it is a write operation, it will invalidate the other CPU cache line corresponding to (meet visibility)

volatile variable rules are a happens-before (the principle of first occurrence) of: the face of this variable read-after-write operations on a variable advance occurred. (This feature can be well explained DCL double check the lock Singleton Why are modified to ensure the safety of concurrent use of the volatile keyword)

to sum up

When the type of a variable declared as volatile, the compiler and runtime will notice this variable is shared, it does not operate on the variables and other memory operation reordering together. volatile variables are not cached in the processor register or to other places invisible, thus always return the most recently written values ​​when reading volatile variable.

Does not perform locking operations when accessing a volatile variable, it does not make the execution thread is blocked, so volatile variable is the keyword more than sychronized lightweight synchronization mechanism.

Locking mechanism can ensure both visibility and atomicity, and volatile variables can only ensure visibility.

Guess you like

Origin www.cnblogs.com/wupeixuan/p/11769011.html