Reprint: http://lucumt.info/posts/java-concurrency/java-volatile-keyword/
The Java keyword is volatile
used to mark a Java variable to be stored in the main , more accurately interpreted as: each time a variable is read volatile
from the computer's main memory read instead of the CPU cache read, each time a volatile
variable is written, will be written to the main memory instead of being written to the CPU cache.
In fact, after Java5, the volatile
keyword is not only used to ensure that the volatile
variable is written to main memory and read from main memory, I will explain in detail in the following chapters:
Volatile Variable Visibility Guarantee
The Java volatile
keyword ensures that volatile
changes to variables are visible in multiple threads. This sounds a bit abstract, and I'll explain in detail next.
In a volatile
multithreaded application that operates on non-variables, because of the performance relationship, each thread may copy variables from the main thread to the CPU cache when reading and writing these variables. If your computer has more than one CPU, each thread may run on a different CPU. This means that each thread may copy the variable to a different CPU's CPU cache, as shown in:
For volatile
variables, the Java Virtual Machine (JVM) does not ensure when data is read from main memory to the CPU cache and when the CPU-cached data is written to main memory. And this may cause some problems that I will explain later.
Assume that two or more threads have access to the following shared variable that contains a counter:
public class SharedObject { public int counter = 0;}
Again, only Thread1 increases the value of the counter variable, but both Thread1 and Thread2 can read the value of the counter variable at any time.
If the couner variable is not declared, it is volatile
not guaranteed when the value in the CPU cache is written to main memory. This means that the value of the counter variable in the CPU cache may not be the same as the value in main memory, as follows:
Causes the thread to not get the variable the latest worthwhile reason is that the variable value is not written back to main memory by other threads in time, which is called visibility issues. Updates to one thread are not visible to other threads.
After the counter variable is declared volatile
, all writes to the counter variable are immediately written to the main memory, and all read operations to the counter variable read the data from the main memory. The following code block shows how to declare a counter variable as volatile
:
public class SharedObject { public volatile int counter = 0;}
Therefore, defining a volatile
variable guarantees that the operation of the write variable is visible to other threads.
The principle of volatile antecedent occurrence
The keyword is volatile
not only used to ensure that variables are read and written from main memory after Java5, in fact, the keyword volatile
has the following effect:
- If thread A writes a
volatile
variable and then thread B reads the same volatile
variable, all the variables that are visible to it before threads A is written volatile
will be visible to it after reading it.
volatile
Read and write instructions for variables cannot be reordered by the JVM (for performance reasons, the JVM may reorder instructions if the JVM detects that the ordering of instructions does not change the program's operation). The instructions before and after can be reordered, but volatile
the read and write of variables cannot be mixed with these reordering instructions. Any instructions following the volatile
read and write of a variable will ensure that it is executed only after the read and write operation of the variable.
The above explanation requires a further explanation.
When a thread writes to a volatile
variable, not only does the volatile
variable itself write to the main memory, but all variables that are affected by the variable are written to the main volatile
memory. When a thread reads to a volatile
variable, it also reads from main memory all variables that are written to the primary volatile
memory along with the variable.
Take a look at the following example:
Thread A: 123; sharedObject.counter = sharedObject.counter + 1;Thread B: int counter = sharedObject.counter; int nonVolatile = sharedObject.nonVolatile;
Because thread a writes a volatile
non-variable sharedobject.nonvolatile before the write operation variable sharedobject.counter volatile
, so when thread A writes the action variable After Sharedobject.counter , the variables sharedobject.nonvolatile and sharedobject.counter are written to main memory.
Since thread B starts with reading the volatile
variable sharedobject.counter , the variable sharedobject.counter and the variable The Sharedobject.nonvolatile will be written to the CPU cache used by thread B. When thread B reads the sharedobject.nonvolatile variable, it will be able to see the variable written by thread A.
Developers can take advantage of the visibility of this extension to optimize the visibility of variables between threads. Unlike having each variable set to volatile
, only a few variables need to be declared volatile
. The following is a simple example program written with this rule Exchanger :
PublicClassExchanger {Private Object object =NullPrivateVolatile hasnewobject =FALSE; public void put (Object newObject) {while ( Hasnewobject) {//waits, does not overwrite new objects that already exist} object = NewObject; hasnewobject = true; //volatile Write} public Object take () {while (!hasnewobject) {//volatile read //waits, does not get the old object (or null object)} object obj = object; Hasnewobject = FALSE; //volatile write return obj;}
Thread A May at any time increase the object by calling the put () method, and thread B May at any time get the object by calling the Take () method. As long as thread A only calls put () and thread B only calls take () , the Exchanger can work with a volatile
variable (excluding synchronized
the use of code blocks).
However, the JVM may reorder Java directives to optimize performance if the JVM can implement this functionality by not altering the semantics of these reordering instructions. What happens if the JVM swaps the read and write instructions in put () and take () ? What happens if the put () is actually executed like this?
while(hasNewObject) { //等待,不覆盖已经存在的新对象}hasNewObject = true; //volatile写入object = newObject;
Note that volatile
the write operation for the variable hasnewobject will be executed before the new variable is actually set, which may appear to be perfectly legal for the JVM. The value of the two write operations instruction is no longer dependent on the other.
However, the execution of command reordering may compromise the visibility of the object variable. First, thread B may read the value of Hasnewobject to True before it writes a value to object that is true to object . Second, there is no guarantee that when the new value of the object will be written to the main memory (well, the next time thread a writes the variable elsewhere ...) volatile
)
To prevent this from happening, the volatile
keyword provides an antecedent principle. The antecedent guarantee ensures that volatile
read and write instructions for variables are not reordered. Instructions may be reordered before and after the program is run, but the volatile
read-write instruction cannot be reordered with any instructions before or after it.
Take a look at the following example:
123;sharedObject.nonVolatile2 = 456;sharedObject.nonVolatile3 = 789;sharedObject.volatile = true; //a volatile variableint someValue1 = sharedObject.nonVolatile4;int someValue2 = sharedObject.nonVolatile5;int someValue3 = sharedObject.nonVolatile6;
The JVM may reorder the first 3 instructions, as long as they all occur before the volatile
write instruction (they must all be volatile
executed before the write instruction).
Similarly, the JVM may reorder the last 3 instructions, as long as the volatile
write instruction precedes them, and none of the 3 instructions can be reordered to volatile
the front of the instruction.
This is volatile
the basic meaning of the principle of antecedent occurrence.
Volatile is not everything.
Although the volatile
keyword ensures that all volatile
read operations for variables are read directly from the main memory, all volatile
writes to the variable are written directly to the master memory, but there are still some cases where defining only one volatile
variable is not enough.
In the previous scenario, thread 1 writes to the shared variable counter
, declaring that the counter variable is followed to volatile
ensure that thread 2 always sees the most recent write value.
In fact, if the value written to the variable does not depend on the value preceding it, multiple threads can even hold the volatile
correct value stored in main memory while writing to a shared variable. In other words, if a thread writes a volatile shared variable, it does not need to read the value of the variable first to calculate the next value.
Once a thread needs to read the value of a volatile
variable first, and then generates the volatile
next value of the shared variable based on that value, the volatile
variable will no longer be able to fully ensure the correct visibility. In a volatile
short interval of time between reading a variable and writing its new value, a race condition arises: multiple threads may read the volatile
same value of the variable, then generate a new value and write to the main memory, which will overwrite each other's values.
This scenario where multiple threads increase the same counter at the same time is volatile
where the variable is not applicable, and the next section is explained in more detail.
Suppose thread 1 reads a shared variable with a value of 0 counter to its CPU cache, adds 1 to it but does not write the added value to main memory. Thread 2 May read the same counter variable from main memory, with a value of 0, and not write it into primary memory, as shown in the following picture:
Thread 1 and thread 2 are not synchronized now, the true value of the shared variable counter should be 2, but in each thread's CPU cache, the value is 1, and the value in main memory is still 0. It's a mess, even if these threads finally write the computed value of the shared variable counter to main memory, the value ofcounter is still wrong.
Application Scenarios of volatile
As mentioned earlier, if two threads read and write to a shared variable at the same time, it volatile
is not enough to use only variables. In this case, you need to use it synchronized
to make sure that the read and write about the variable are atomic operations. Reading or writing a volatile
variable does not block other threads from reading and writing the variable. In this case, you must use keywords synchronzied
to decorate your critical code.
In addition to using synchronzied
, you can also use some of the atomic data types in the Java.util.concurrent package, such as Atomiclong, Atomicreference, and so on.
When only one thread reads volatile
and writes a variable and other threads read only that variable, volatile
you can ensure that the read thread reads the most recent write value of the variable. If the variable is not declared volatile
, then these read threads cannot guarantee that the read is the most recent write value.
Volatile
Keywords apply to 32-bit variables and 64-bit variables.
Volatile performance considerations
Since volatile
both the read and write of variables are directly from the main memory, it is more expensive to read and write directly to the main memory relative to the CPU cache, and access to a volatile
variable also prevents instructions from reordering, and instruction sequencing is a common performance enhancement technique. Therefore, you should use variables only when you really need to ensure the visibility of variables volatile
The volatile Java Learning