The Volatile keyword in Java is used to indicate that a variable is “stored in main memory.” This means that each read of a volatile variable is read from outgoing memory rather than from the CPU cache. Each write to a volatile variable updates the corresponding CPU cache as well as main memory.
In fact, since JDK1.5, the volatile keyword provides additional functionality.
Visibility problem
The volatile keyword in Java ensures that changes to the value of a variable are visible to multiple threads.
In multithreaded applications, when multiple threads operate on a non-volatile variable, each thread copies the value of the variable from main memory to the CPU cache for performance reasons (CPU cache is faster than main memory). When the program is deployed on a multi-CPU machine, each thread is actually allowed on a different CPU, which causes each thread to copy the variable into the cache of the CPU on which it is running, as shown in the figure below:
CPU cache, thread, main memory diagram
When using non-volatile variables, there is no guarantee that reads and writes to the variable will work in a multithreaded JVM.
private static boolean flag; public static void main(String[] args) throws Exception { Thread t1 = new Thread() { @Override public void run() { while (true) { if (flag) { System.out.println("I am thread1, flag is true"); flag = false; }}}}; t1.start(); Thread t2 = new Thread() { @Override public void run() { while (true) { if (flag == false) { System.out.println("I am thread2, flag is false"); flag = true; }}}}; t2.start(); }Copy the code
We started two threads, hoping that they would run printing alternately, but stopped printing after the program had been running for a while. The reason is that the CPU cache for each thread has a copy of the flag variable, and the calculation is based on the values in the CPU cache. The JVM does not guarantee that changes made by each thread to the flag will be immediately reflected by other threads.
Volatile guarantees visibility
Let’s see what happens if we add volatile to the code above:
private volatile static boolean flag; public static void main(String[] args) throws Exception { Thread t1 = new Thread() { @Override public void run() { while (true) { if (flag) { System.out.println("I am thread1, flag is true"); flag = false; }}}}; t1.start(); Thread t2 = new Thread() { @Override public void run() { while (true) { if (flag == false) { System.out.println("I am thread2, flag is false"); flag = true; }}}}; t2.start(); }Copy the code
The reason is that when a variable declares the volatile keyword, changes made by one thread to the variable update the CPU cache on which the thread is running as well as main memory, invalidating copies of the variable in other CPU caches and re-reading the latest value from main memory.
The guarantee of order by volatile
JVM and CPU code can be rearranged for performance reasons, and some optimizations have been made since JDK1.5. Let’s look at the following code snippet:
private static int a;
private static int b;
private static volatile int c;
private static int d;
private static int e;
public static void testOrder() {
a = 1;
b = 2;
c = 3;
d = 4;
e = 5;
}
Copy the code
Prior to JDK1.5, the system might execute testOrder methods in this order:
e = 5;
d = 4;
c = 3;
b = 2;
a = 1;
Copy the code
After JDK1.5, we made some order optimizations so that a and B must be executed before C (a and B can be swapped), and D and E need not be executed after C (d and E can be swapped).
So what does this do? Let’s look at the following code:
private boolean init = false;
//ServiceA
public void init(){
// do some init work
initServiceA()
init = true;
}
//ConsumeA
if(init){
// do some work
ServiceA.doSomething();
}
Copy the code
If init is not volatile, the actual program runs init = true; It can be executed before initServiceA(), which results in SerivceA being called by the Consumer before it has been initialized.
Nonatomic operation
Volatile does not guarantee consistency of nonatomic operations, as shown in the following code:
private static volatile int value = 0; public static void main(String[] args) { Runnable run = new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { value++; }}}; Thread t1 = new Thread(run); Thread t2 = new Thread(run); t1.start(); t2.start(); while(Thread.activeCount() > 1) { Thread.yield(); } System.out.println(value); }Copy the code
Here we expect the value variable to get 2000 after 1000 increments in two threads, but we can only get a value less than 2000. Why? Because value++ is not an atomic operation when executed by the CPU, it is roughly divided into three steps:
1. Read the value from the main memory and store it in the CPU cache
2. Add 1 to value
3. Assign the value after value is added by 1 to value, and then update the main memory
This may result in the above code error in the case of multiple threads.
Volatile and CAS (pronounced Unsafe) objects are the building blocks for Java and JUC (Pronounced Unsafe).
Demo code location
Java – Learning: The Java Learning Case – Gitee.com
Reference:
Tutorials.jenkov.com/java-concur… .