Java multithreading (3) volatile domain

Source: Internet
Author: User

Java multithreading (3) volatile domain
Preface

Sometimes synchronization is used only to read and write one or two instance domains, and the overhead is too large. The volatile keyword provides a lock-free mechanism for Synchronous access to the instance domain. If you declare a domain as volatile, the compiler and virtual machine will know that this domain may be concurrently updated by another thread. Before talking about the volatile keyword, we need to understand the concept of the Memory Model and the three features in concurrency programming: atomicity, visibility, and orderliness.

1. java Memory Model and atomicity, visibility and orderliness

The Java memory model requires that all variables exist in the primary memory, and each thread has its own working memory. All operations on variables by the thread must be performed in the working memory, rather than directly performing operations on the main memory. In addition, each thread cannot access the working memory of other threads.
In java, execute the following statement:

int i=3;

The execution thread must assign values to the cache row where variable I is located in its own working thread, and then write the values to the primary memory. Instead of Directly Writing value 3 to the primary storage.
What guarantee does Java provide for Atomicity, visibility, and orderliness?

Atomicity

Reading and assigning values to variables of the basic data type are atomic operations, that is, these operations cannot be interrupted, either executed or not executed.
Let's take a look at the following code:

X = 10; // statement 1y = x; // statement 2x ++; // statement 3x = x + 1; // Statement 4

Only Statement 1 is an atomic operation, and none of the other three statements is an atomic operation.
Statement 2 actually contains two operations. It first reads the value of x and then writes the value of x to the working memory, although reading the value of x and writing the value of x to the working memory are atomic operations, they are not atomic operations.
Similarly, x ++ and x = x + 1 include three operations: Read the value of x, add 1, and write new values.
That is to say, only simple reading and assigning values (and must assign values to a variable, and mutual assigning values between variables is not an atomic operation) is an atomic operation.
Many classes in the java. util. concurrent. atomic package use very efficient machine-level commands (rather than locking) to ensure the atomicity of other operations. For example, the AtomicInteger class provides methods incrementAndGet and decrementAndGet, which are atomic to auto-increment and auto-Subtract an integer. The AtomicInteger class can be safely used as a shared counter without synchronization.
In addition, this package contains atomic classes such as AtomicBoolean, AtomicLong, and AtomicReference, which are only used by system programmers who develop concurrent tools. application programmers should not use these classes.

Visibility

Visibility refers to the visibility between threads. The State modified by one thread is visible to another thread. That is, the result of a thread modification. The other thread can be seen immediately.
When a shared variable is modified by volatile, it ensures that the modified value is immediately updated to the primary storage, so it is visible to other threads. When other threads need to read the variable, it reads the new value from the memory.
The visibility of common shared variables cannot be guaranteed, because after the common shared variables are modified, it is not clear when they are written to the primary storage. When other threads read the shared variables, in this case, the memory may still be the old value, so visibility cannot be guaranteed.

Orderliness

In the Java memory model, the compiler and processor are allowed to re-Sort commands. However, the re-sorting process does not affect the execution of a Single-threaded program, but affects the correctness of concurrent execution of multiple threads.
The volatile keyword can be used to ensure certain "orderliness ". In addition, synchronized and Lock can be used to ensure orderliness. Obviously, synchronized and Lock ensure that a thread executes the synchronization code at each time point, which means that the thread executes the synchronization code in sequence, naturally, order is guaranteed.

2. volatile keywords

Once a shared variable (the member variable of the class and the static member variable of the class) is modified by volatile, it has two layers of semantics:

This ensures the visibility when different threads operate on this variable. That is, a thread modifies the value of a variable. This new value is immediately visible to other threads. Command Re-sorting is prohibited.

First read a piece of code. If thread 1 is executed first and thread 2 is executed later:
  

// Thread 1 boolean stop = false; while (! Stop) {doSomething () ;}// thread 2 stop = true;

Many people may use this flag method when thread interruption occurs. But in fact, will this Code fully run correctly? Will the thread be interrupted? Not necessarily. In most cases, this code can interrupt the thread, but it may also lead to thread interruption failure (although this possibility is very small, but once this happens, it will lead to an endless loop ).
Why can't threads be interrupted? Each thread has its own working memory during running. When thread 1 is running, it copies the value of the stop variable and stores it in its own working memory. So when thread 2 changes the value of the stop variable, but it has not been able to write it into the main memory, thread 2 is re-running to do other things, because thread 1 does not know the changes to the stop variable of thread 2, it will keep repeating.
However, modified with volatile will become different:

If the volatile keyword is used, the modified value is immediately written to the primary storage. If the volatile keyword is used, when thread 2 is modified, the cache row with the stop variable cached in the working memory of thread 1 is invalid. The cache row with the stop variable cached in the working memory of thread 1 is invalid, so when thread 1 reads the stop value of the variable again, it will read it from the primary storage. Does volatile ensure atomicity?

We know that the volatile keyword ensures the operation visibility, but can volatile ensure that the operation on variables is atomic?

Public class Test {public volatile int inc = 0; public void increase () {inc ++;} public static void main (String [] args) {final Test test = new Test (); for (int I = 0; I <10; I ++) {new Thread () {public void run () {for (int j = 0; j <1000; j ++) test. increase ();};}. start () ;}// ensure that all the preceding threads have completed the while (Thread. activeCount ()> 1) Thread. yield (); System. out. println (test. inc );}}

The results of each running of this Code are inconsistent, and they are all numbers smaller than 10000. As mentioned above, the auto-increment operation is not atomic, it includes reading the original value of the variable, adding 1, and writing to the working memory. That is to say, the three sub-operations of the auto-increment operation may be split and executed.
If the value of the variable inc is 10 at a certain time, thread 1 will perform auto-increment operations on the variable. Thread 1 first reads the original value of the variable inc, and thread 1 is blocked; then thread 2 performs the auto-increment operation on the variable, and thread 2 also reads the original value of the variable inc. Because thread 1 only reads the variable inc but does not modify the variable, therefore, the cache row of the cache variable inc in the working memory of thread 2 will not be invalid. Therefore, thread 2 will directly read the inc value from the main memory and find that the inc value is 10, then add 1, write 11 to the working memory, and write it to the primary memory. Thread 1 then adds 1. Since the inc value has been read, note that the inc value is still 10 in the worker memory of thread 1, therefore, after thread 1 adds 1 to inc, the value of inc is 11, then 11 is written to the working memory, and finally to the main memory. After the two threads perform an auto-increment operation, inc only increases by 1.
The auto-increment operation is not atomic, and volatile cannot guarantee that any operation on the variable is atomic.

Can volatile ensure orderliness?

As mentioned above, the volatile keyword can disable command re-sorting, so volatile can ensure orderliness to a certain extent.
The volatile keyword disables Command Re-sorting. It has two meanings:

When the program executes the read or write operations on the volatile variable, all the changes in the previous operations must have been completed, and the results are visible to subsequent operations; the subsequent operations are certainly not yet performed; During command optimization, statements that access volatile variables cannot be placed behind them for execution, you cannot put the statement after the volatile variable in front of it for execution. 3. Use the volatile keyword correctly

The synchronized keyword prevents multiple threads from executing a piece of code at the same time, which affects the program execution efficiency. In some cases, the volatile keyword outperforms synchronized in performance, however, note that the volatile keyword cannot replace the synchronized keyword, because the volatile keyword cannot guarantee the atomicity of the operation. Generally, volatile must meet the following two conditions:

The write operation on the variable does not depend on the current value. The variable is not included in the variant with other variables.

The first condition is that it cannot be auto-incrementing, auto-subtraction, or other operations. volatile does not guarantee atomicity.
In the second condition, let's take an example. It contains a non-variant formula: the lower bound is always less than or equal to the upper bound.

public class NumberRange {    private volatile int lower, upper;    public int getLower() { return lower; }    public int getUpper() { return upper; }    public void setLower(int value) {         if (value > upper)             throw new IllegalArgumentException(...);        lower = value;    }    public void setUpper(int value) {         if (value < lower)             throw new IllegalArgumentException(...);        upper = value;    }}

This method limits the range of state variables. Therefore, defining the lower and upper fields as volatile type does not fully implement the thread security of the class, so synchronization is still required. Otherwise, if the two threads use inconsistent values to execute setLower and setUpper at the same time, the range will be in an inconsistent state. For example, if the initial status is (0, 5), thread A calls setLower (4) at the same time and thread B calls setUpper (3 ), obviously, the values of the two operations do not meet the conditions, so both threads will pass the check for the non-variant protection so that the final range value is (4, 3 ), this is obviously incorrect.
Volatile can be used to ensure the atomicity of operations. volatile has two main scenarios:

Status flag
volatile boolean shutdownRequested;...public void shutdown() {  shutdownRequested = true;  }public void doWork() {     while (!shutdownRequested) {         // do stuff    }}

It is very likely that the shutdown () method will be called from outside the loop-that is, in another thread-therefore, you need to execute some synchronization to ensure correct visibility of the shutdownRequested variable. However, writing a loop using synchronized blocks is much more difficult than writing a loop using the volatile status flag. Because volatile simplifies encoding and the status flag does not depend on any other State in the program, volatile is ideal for this scenario.

Dual Check Mode (DCL)
public class Singleton {      private volatile static Singleton instance = null;      public static Singleton getInstance() {          if (instance == null) {              synchronized(this) {                  if (instance == null) {                      instance = new Singleton();                  }              }          }          return instance;      }  }  

Using volatile will affect the performance more or less, but considering the correctness of the program, it is worthwhile to sacrifice this performance.
DCL has the advantage of high resource utilization. When getInstance is executed for the first time, the single-instance object is instantiated, which is highly efficient. The disadvantage is that the response is a little slower during the first loading, and there are also some defects in the high concurrency environment, although the probability of occurrence is very small.
Although DCL solves the problems of resource consumption, redundant synchronization, and thread security to a certain extent, it still becomes invalid in some situations, that is, DCL becomes invalid, we recommend that you use the following code (static internal class Singleton mode) to replace DCL in java concurrent programming practices:

public class Singleton {     private Singleton(){    }      public static Singleton getInstance(){          return SingletonHolder.sInstance;      }      private static class SingletonHolder {          private static final Singleton sInstance = new Singleton();      }  } 
4. Summary

Compared with the lock, the Volatile variable is a very simple but fragile synchronization mechanism that will provide better performance and scalability than the lock in some cases. If you strictly follow the volatile usage condition that variables are truly independent from other variables and their previous values, you can use volatile instead of synchronized in some cases to simplify the code. However, code using volatile is often more error-prone than code using locks. This article describes the two most common use cases that can replace synchronized with volatile. In other cases, we 'd better use synchronized.

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.