The following information is transferred from http://tutorials.jenkov.com/java-concurrency/volatile.html (using Google Translate):
The Java volatile
keyword is used to mark Java variables as "stored in primary storage". More precisely, this means that each read volatile variable is read from the main memory of the computer, not from the CPU cache, and every write to the volatile variable is written to the primary storage, not just to the CPU cache.
In fact, Java 5 's volatile
keyword guarantees that not only volatile variables are written and read from main memory. I'll explain in the following sections.
Java Volatile Visibility Assurance
The Java volatile
keyword ensures the visibility of changes across threads to variables. This may sound a bit abstract, so let me elaborate.
For performance reasons, threads can replicate variables from primary storage to the CPU cache in multithreaded applications that run on non-volatile variables. If your computer contains multiple CPUs, each thread may run on a different CPU. This means that each thread can replicate the variables to the CPU cache of different CPUs. This is illustrated here:
For non-volatile variables, there is no guarantee that the Java Virtual Machine (JVM) reads data from the primary storage into the CPU cache, or writes the data from the CPU cache to the primary storage. This may cause several problems that I will explain in the following sections.
Imagine that two or more threads can access the shared object, which contains a counter variable that looks like this:
Public class Sharedobject { publicint counter = 0;}
Imagine that only thread 1 increments the counter
variable, but thread 1 and thread 2 are likely to counter
read variables from moment to moment.
If a counter
variable is not declared, volatile does not guarantee that the counter
value of the variable is written back to the primary storage from the CPU cache. This means that the counter在
value of the variable in the CPU cache may not be the same as the main memory. This situation is illustrated here:
You do not see the latest value of the variable because the problem of a thread that has not yet been written back to the main memory by another thread is called a "visibility" issue. Updates to one thread are not visible to other threads.
By declaring a counter
variable, all writes to the variable are volatile
counter
immediately written back to main memory. In addition, counter
all reads from the variable are read directly from the primary memory. Here's how to volatile
declare a counter
variable:
Public class Sharedobject { publicvolatileint counter = 0;}
Therefore, declaring a volatile variable can guarantee the visibility of other write threads of the variable.
Java Volatile Event Assurance
Because the Java 5 volatile
keyword does not only guarantee read and write to the main memory of the variable. In fact, the volatile
keyword guarantees:
If thread A writes a volatile variable and thread B then reads the same volatile variable and then sees all the variables of thread A before it writes the volatile variable, it will also be visible after thread B, which has read the volatile variable.
Read and write instructions for volatile variables cannot be reordered by the JVM (as long as the JVM does not detect changes in program behavior from reordering, the JVM may reorder instructions for performance reasons). The previous and subsequent instructions can be reordered, but these directives cannot be mixed to write or write. Regardless of whether the volatile variable is read or written, any instruction will be guaranteed to occur after reading or writing.
These statements require more in-depth explanations.
When a thread writes a volatile variable, it not only writes the volatile variable itself to the main memory. All other variables that were changed by the thread are flushed to the main storage before the volatile variable is written. When a thread reads a volatile variable, it also reads all other variables that are flushed to the main storage along with the volatile variable from the main memory.
See this example:
Thread A: = 123; Sharedobject.counter = sharedobject.counter + 1; Thread B: int counter = sharedobject.counter; int nonvolatile = sharedobject.nonvolatile;
Because thread a writes a non-volatile variable sharedobject.nonvolatile before writing the volatile variable Sharedobject.counter, when thread a writes Sharedobject.counter (volatile variable), s Both Haredobject.nonvolatile and Sharedobject.counter are written to the main memory.
Because thread B starts from reading volatile sharedobject.counter, both Sharedobject.counter and Sharedobject.nonvolatile read from main memory to the CPU cache used by thread B. When thread B reads Sharedobject.nonvolatile, it sees that the value is written by thread A.
Developers can use this extended visibility guarantee to optimize the visibility of variables between threads. Instead of declaring each variable volatile
, you only need to declare one or several variables volatile
. Here is an example of a simple Exchanger
class:
Public classExchanger {PrivateObject object =NULL; Private volatileHasnewobject =false; Public voidput (Object newObject) { while(hasnewobject) {//wait-do not overwrite existing new object} object=NewObject; Hasnewobject=true;//volatile Write } PublicObject Take () { while(!hasnewobject) {//volatile Read//Wait-don ' t take the old object (or null)} Object obj=object; Hasnewobject=false;//volatile Write returnobj; }}
Thread A may occasionally set an object by calling put (). Thread B may occasionally fetch objects by calling take (). As long as thread A calls put () and only thread B calls take (), the exchanger can use a volatile variable (without using a synchronous block) to work properly.
However, the JVM can reorder Java instructions to optimize performance if the JVM can not change the semantics of the reordering instructions. What happens if the JVM switches the read and sequential writes inside, put()
and take()
? If you put()
do, do the following:
while (hasnewobject) { //wait-do not overwrite existing new objecttrue // volatile Writeobject = NewObject;
Note that the write to the volatile variable hasnewobject will be executed before the new object is actually set. For the JVM, this may seem completely valid. The values of two write instructions do not depend on each other.
However, reordering instruction execution can compromise the visibility of object variables. First, thread B may actually see Hasnewobject set to True before it writes a new value to the object variable. Second, it is not even guaranteed that the new value written to the object will be flushed back to main memory (the following is the case where thread a writes a volatile variable somewhere).
To prevent this from happening, the volatile keyword has "occurred before guarantee". Events that occurred prior to the guarantee ensure that read and write instructions for volatile variables cannot be reordered. The previous and subsequent directives can be reordered, but the volatile read/write instructions cannot be reordered by any instructions that occur before or after them.
See this example:
Sharedobject.nonvolatile1 = 123= 456= 789; sharedobject. volatile true // a volatile variable int someValue1 = sharedobject.nonvolatile4; int someValue2 = sharedobject.nonvolatile5; int someValue3 = sharedobject.nonvolatile6;
The JVM may reorder the first 3 instructions as long as all of these instructions occur before the volatile write instruction (they must be executed before the volatile write instruction).
Similarly, the JVM can reorder the last 3 instructions as long as a volatile write instruction occurs before all of these instructions. None of the last 3 instructions can be reordered until the volatile write instruction.
This is basically the meaning of the volatile that occurred before Java protection.
Volatile is not always enough
Even if the volatile
keyword guarantees that volatile
all reads from the variable are read directly from the main memory, and volatile
all writes to the variable are written directly to master memory, there is still not enough to declare the volatile variable.
In the case described earlier, only thread 1 writes a shared counter
variable, declaring counter
A variable volatile
is sufficient to ensure that thread 2 always sees the most recent write value.
In fact, volatile
if the new value of the write variable does not depend on its previous value, multithreading may even write a shared variable and still have the correct value stored in the primary storage. In other words, if a thread that writes a value to a shared volatile
variable first does not need to read its value to find its next value.
Once a thread needs to read the volatile
value of a variable first and generate a new value for a shared variable based on that value volatile
, the variable is volatile
no longer sufficient to guarantee proper visibility. volatile
a short gap between reading a variable and writing a new value creates a race condition where multiple threads may read the same volatile
variable value, generate a new value for the variable, and write the value back to main memory-overwriting each other's values.
This is the case where multithreading increases the same counter, where volatile
variables are not sufficient. The following sections explain this situation in more detail.
Imagine if thread 1 reads a shared variable of counter value 0 into its CPU cache and increments it to 1 instead of writing the changed value to the primary storage. Thread 2 can then read the same variable from the value of the counter
variable to its own CPU cache, still 0 of the primary memory. Thread 2 can also increment the counter to 1, nor write it back to the main storage. This situation is as follows:
Thread 1 and Thread 2 are now virtually out of sync. The counter
actual value of the shared variable should be 2, but the value of the variable in the CPU cache for each thread is 1, and the value in main memory is still 0. It's a mess! Even if the thread eventually counter
writes the value of the shared variable back to main memory, the value will be wrong.
When do you use it?
As mentioned earlier, if two threads are read and written for shared variables, it volatile
is not sufficient to use the keyword. In this case, you need to use synchronized to ensure that the read and write of the variable is atomic. Reading or writing a volatile variable does not prevent the thread from reading or writing. To achieve this, you must use the Synchronized keyword around the critical section.
As an synchronized
alternative to the block, you can also use java.util.concurrent
one of the many atomic data types found in the package. For example, AtomicLong
or AtomicReference
one of the others.
If only one thread reads and writes the value of a volatile variable, and other threads read only the variable, the read thread is guaranteed to see the most recent value written to the volatile variable. This is not guaranteed in the case of invariant changes.
This volatile
keyword is guaranteed to work on 32-bit and 64 variables.
Performance considerations Fluctuate
Reading and writing volatile variables can cause variables to be read or written to the master memory. Reading and writing to main memory is more expensive than accessing the CPU cache. Accessing volatile variables can also prevent instructions from reordering, which is a normal performance enhancement technique. Therefore, when you really need to enforce the visibility of variables, you should only use volatile variables.
13. Java Concurrency and multithreading-java volatile keywords