Related articles
Java multithreading (i) Thread definitions, states, and properties
Java Multithreading (ii) synchronization
Android multithreaded (one) thread pool
Android Multithreading (ii) Asynctask source analysis
Preface
Sometimes it is too expensive to use synchronization just to read or write one or two instance domains, and the volatile keyword provides a lock-free mechanism for synchronizing access to the instance domain. If you declare a domain to be volatile, then the compiler and the virtual machine know that the domain is likely to be updated concurrently by another thread. Before we talk about the volatile keyword, we need to look at the related concepts of the memory model and the three features in concurrent programming: atomicity, Visibility, and ordering.
1. Java memory model and atomicity, visibility and ordering
The Java memory model stipulates that all variables are present in main memory, and each thread has its own working RAM. All the operations of a thread on a variable must be done in working memory, not directly on main storage. And each thread cannot access the working memory of other threads.
In Java, execute the following statement:
int i=3;
The execution thread must first assign a value to the cache row where the variable I resides in its own worker thread before writing to the host. Instead of writing the value 3 directly into main memory.
So what are the guarantees that the Java language itself provides for atomicity, visibility, and ordering?
Atomic Nature
Read and assign operations to variables of the base data type are atomic operations, meaning that these operations are not interrupted, executed, or not executed.
Take a look at the following code:
10; //语句1y = x; //语句2x++; //语句31; //语句4
Only Statement 1 is an atomic operation, and the other three statements are not atomic in nature.
Statement 2 actually contains 2 operations, it first to read the value of x, and then write the value of x to the working memory, although reading the value of x and the value of x to the working memory of the 2 operations are atomic operations, but together is not atomic operation.
Similarly, X + + and × = x+1 include 3 operations: reads the value of x, adds 1 operations, and writes a new value.
That is, only a simple read, assignment (and must assign a number to a variable, the reciprocal assignment of a variable is not an atomic operation) is the atomic operation.
There are many classes in the Java.util.concurrent.atomic package that use very efficient machine-level instructions (rather than using locks) to ensure the atomicity of other operations. For example, the Atomicinteger class provides methods Incrementandget and Decrementandget, which atomically increment and decrement an integer by itself. You can safely use the Atomicinteger class as a shared counter without needing to synchronize.
The package also contains Atomicboolean,atomiclong and atomicreference these atomic classes are intended for use only by system programmers developing concurrency tools and should not be used by application programmers.
Visibility of
Visibility refers to the visibility between threads, and the state of one thread modification is visible to another thread. That is, the result of a thread modification. Another thread can see it right away.
When a shared variable is modified by volatile, it guarantees that the modified value is immediately updated to main memory, so it is visible to other threads, and when another thread needs to read it, it will read the new value in memory.
The common shared variable does not guarantee visibility, because when a common shared variable is modified, it is indeterminate when it is written to main memory, and when other threads go to read it may be the original old value at this time and therefore cannot guarantee visibility.
Order of
In the Java memory model, the compiler and processor are allowed to reorder instructions, but the reordering process does not affect the execution of a single-threaded procedure, but it can affect the correctness of multithreaded concurrency execution.
The volatile keyword can be used to guarantee a certain "order." It is also possible to maintain order through synchronized and lock, and it is clear that synchronized and lock ensure that each time a thread executes the synchronous code, which is the equivalent of allowing the thread to execute the synchronization code in order, naturally guaranteeing order.
2. Volatile keyword
Once a shared variable (a member variable of a class, a static member variable of a class) is modified by volatile, then there are two layers of semantics:
- The visibility of this variable is ensured by a thread that modifies the value of a variable, which is immediately visible to other threads.
- command reordering is prohibited.
Look at a piece of code first, if thread 1 executes first, thread 2 executes:
//线程1booleanfalse;while(!stop){ doSomething();}//线程2true;
Many people may use this tagging method when they break a thread. But in fact, is this code going to work exactly right? Is it bound to break the thread? Not necessarily, perhaps most of the time, this code can break the thread, but it can also lead to the inability to break threads (although this is a very small possibility, but as soon as this happens it will cause a dead loop).
Why is it possible to break the thread? Each thread has its own working memory as it runs, and when thread 1 is running, a copy of the value of the stop variable is placed in its own working memory. Then when thread 2 changed the value of the stop variable, but before it could write to the main memory, thread 2 went to do something else, and thread 1 would have been looping because it did not know that thread 2 had changed the stop variable.
But it becomes different after the volatile modification:
First: Using the volatile keyword forces the modified value to be immediately written to main memory;
Second: With the volatile keyword, when thread 2 is modified, it causes thread 1 to work in-memory cache variable stop cache row is invalid;
Third: Because thread 1 works in-memory cache variable stop cache line is invalid, so thread 1 reads the value of the variable stop again to read the main memory.
does volatile guarantee atomicity?
We know that the volatile keyword guarantees the visibility of the operation, but does volatile guarantee that the operation of the variable is atomic?
Public classTest { Public volatile intinc =0; Public void Increase() {inc++; } Public Static void Main(string[] args) {Final Test test =NewTest (); for(intI=0;i<Ten; i++) {NewThread () { Public void Run() { for(intj=0;j< +; j + +) Test.increase (); }; }.start (); }//Ensure that the previous thread finishes executing while(Thread.activecount () >1) Thread.yield(); System. out. println (Test.inc); }}
This code every time the result is inconsistent, is a number less than 10000, as mentioned earlier, the self-increment operation is not atomic, it includes reading the original value of the variable, adding 1 operations, writing to the working memory. Then the three sub-operations of the self-increment operation may be split to execute.
If a time variable inc has a value of 10, thread 1 self-increment the variable, thread 1 reads the original value of the variable inc, then thread 1 is blocked, then thread 2 does the self-increment of the variable, thread 2 also reads the original value of the variable inc, because thread 1 reads only the variable Inc. Instead of modifying the variable, it does not cause the cache line of the working in-memory cache variable inc of thread 2 to be invalid, so thread 2 will go directly to main memory to read the value of INC, find the value of INC 10, then add 1 operations and write 11 to the working memory, and finally to main memory. Then thread 1 then adds 1, since the value of the INC has been read, note that at this point the value of the in-Memory inc of Thread 1 is still 10, so that Threads 1 to Inc 1, and then 11 to the working memory, and finally to main storage. Then, after two threads had a single self-increment operation, Inc increased by 1.
The self-increment operation is not atomic, and volatile does not guarantee that any operation on the variable is atomic.
Does volatile guarantee order?
It is mentioned that the volatile keyword can prohibit order reordering, so volatile can be guaranteed to a certain degree of order.
The volatile keyword prohibit command reordering has two layers of meaning:
-When a program executes a read or write operation to a volatile variable, the change in its preceding operation must have been made, and the result is already visible to the subsequent operation;
-When you do a command optimization, you cannot put the statements that are accessed by the volatile variable behind it, and you cannot put the statements that follow the volatile variable in front of them.
3. Use the volatile keyword correctly
The Synchronized keyword is to prevent multiple threads from executing a piece of code at the same time, which can affect program execution efficiency, while the volatile keyword has better performance than synchronized in some cases. Note, however, that the volatile keyword cannot replace the synchronized keyword because the volatile keyword does not guarantee the atomicity of the operation. In general, the following 2 conditions are required to use volatile:
-The write to the variable does not depend on the current value
-the variable is not included in the invariant with other variables
The first condition is that it cannot be a self-increment or decrement operation, and the above mentioned that volatile does not guarantee atomicity.
The second condition, let's give an example. It contains an invariant: The Nether is always less than or equal to the upper bound
Public classNumberrange {Private volatile intLower, Upper; Public int Getlower() {returnLower } Public int Getupper() {returnUpper } Public void Setlower(int value) {if(value> Upper)Throw NewIllegalArgumentException (...); Lower =value; } Public void Setupper(int value) {if(value< lower)Throw NewIllegalArgumentException (...); Upper =value; }}
This approach restricts the state variables of the scope, so defining the lower and upper fields as volatile types does not fully implement the thread safety of the class, and it still requires synchronization. Otherwise, if it happens that two threads execute Setlower and setupper at the same time using inconsistent values, the scope will be in an inconsistent state. For example, if the initial state is (0, 5), and thread A calls Setlower (4) at the same time, and thread B calls Setupper (3), it is clear that the values crossed by these two operations are not eligible, then two threads pass the check to protect the invariant, so that the final range value is (4 , 3), which is obviously wrong.
In fact, to ensure the atomicity of the operation can use volatile, using volatile mainly has two scenarios:
Status Flag
volatileboolean shutdownRequested;...publicvoidshutdown() { true; }publicvoiddoWork() { while (!shutdownRequested) { // do stuff }}
It is possible to call the shutdown () method from outside the loop-that is, in another thread-so you need to perform some kind of synchronization to ensure that the visibility of the shutdownrequested variable is implemented correctly. However, it is much more cumbersome to write loops using synchronized blocks than to write with volatile state flags. Because volatile simplifies encoding and the status flag does not depend on any other state within the program, it is well suited to use volatile.
double 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; } }
The
Use of volatile here can affect performance more or less, but it is worthwhile to sacrifice this performance, given the correctness of the program. The advantage of the
DCL is that the resource utilization is high, and the singleton object is instantiated and efficient the first time the getinstance is executed. The disadvantage is that the first load reaction slightly slower, in high concurrency environment also has a certain defect, although the probability of occurrence is very small. While the
DCL solves some problems such as resource consumption and redundant synchronization, line Cheng, etc., he still fails in some cases, that is, the DCL fails, and in the Java Concurrency Programming practice, the following code (static internal class singleton mode) is suggested to replace the DCL:
publicclass Singleton { privateSingleton(){ } publicstaticgetInstance(){ return SingletonHolder.sInstance; } privatestaticclass SingletonHolder { privatestaticnew Singleton(); }
About double check can view http://blog.csdn.net/dl88250/article/details/5439024
4. Summary
Volatile variables are a very simple, yet very fragile synchronization mechanism compared to locks, which in some cases provides better performance and scalability than locks. If you strictly follow the use of volatile conditions where variables are really independent of other variables and their previous values, in some cases you can use volatile instead of synchronized to simplify the code. However, code that uses volatile is often more error-prone than code that uses locks. This article describes the two most common use cases where you can use volatile instead of synchronized, and we'd better use synchronized in other situations.
Java Multithreading (iii) volatile domain