I heard that wechat search “Java fish” really can be stronger oh!
(1) Overview
To understand concurrent programming, you first need to understand the three characteristics of concurrent programming: visibility, atomicity, and orderliness. Volatile guarantees visibility and order, but not atomicity. A few pieces of code and diagrams will reinforce your understanding of volatile.
(2) Volatile guarantees visibility
When we talked about the JMM, we wrote A program and learned that the operation data between two different threads is invisible, that is, thread B changes A shared variable in main memory, and thread A does not know about it. This is the invisibility between threads.
public class Test {
private static boolean flag=false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run(a) {
System.out.println("waiting");
while(! flag){} System.out.println("in");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run(a) {
System.out.println("change flag");
flag=true;
System.out.println("change success"); } }).start(); }}Copy the code
The result of this code is that the second thread changes the value of flag without being seen by the first thread
Now let’s make a small change and add the volatile modifier to Flag
private static volatile boolean flag=false;
Copy the code
Once again, the JMM is needed to understand the volatile principle. Here is the flow chart of the first code:
This is the non-volatile execution and will end up at step 10, where flag will be true, but thread A does not know.
With volatile, when the main memory flag is changed, volatile invalidates the data of other threads that are also using the variable through the CPU’s bus sniffing mechanism, causing them to re-read the main memory values, and thread A to discover that the flag has changed.
(3) Volatile ensures order
Volatile ensures ordered execution by preventing instruction reordering through memory barriers. I covered this in instruction reorder and memory barriers, if you’re interested.
(iv) Volatile does not guarantee atomicity
Start with a simple piece of code that defines a count and accessorizes with volatile. Then create ten threads, each loop 1000 times count++ :
public class VolatileAtomSample {
private static volatile int count=0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run(a) {
for (int j = 0; j < 1000; j++) {
count++;
}
}
}).start();
}
try {
Thread.sleep(1000);
} catch(InterruptedException e) { e.printStackTrace(); } System.out.println(count); }}Copy the code
In the end, no matter how many times you execute it, you’ll find that the final output count is almost always less than 10000. Volatile does not guarantee atomicity, but why?
Again, this is represented by A flow chart. When thread A completes count+1, it writes the value back to main memory. At this point, count in other working memory is invalidated and reassigned due to the visibility of volatile.
If thread B has just reached step 4, count in thread B’s working memory will be volatile to 1, assign count to 1, and count++ will be missing. This is why volatile does not guarantee atomicity.
(v) Use scenarios of Volatile
Here’s a classic example of how volatile can be used:
public class Singleton {
private static Singleton Instance;
private Singleton(a){};
public static Singleton getInstance(a){
if (Instance==null) {synchronized (Singleton.class){
Instance=newSingleton(); }}returnInstance; }}Copy the code
The above code is sure to be familiar with the singleton pattern of the most classic piece of code, if the instance object is empty initialize, if not empty return instance, looks fine, but in high concurrency environment this code can be problematic. Instance=new Singleton(); To instantiate an object, there are three steps:
Note that instruction reordering is possible in these three steps, so it is possible to allocate memory, then assign objects to memory, and finally instantiate objects. Step 1 -> Step 3 -> Step 2.
When there are two threads A and B applying for objects at the same time, when thread A performs the second step after reordering
Thread B executes if (Instance==null) and returns Instance because it has already been assigned to memory. But! The object is not instantiated, so when thread B calls the instance, an error is reported.
The solution to this problem is for volatile to disallow reordering
private static volatile Singleton Instance;
Copy the code
(vi) Possible problems caused by Volatile
There is a limit to everything, and so is volatile. If a program uses a lot of volatile, it can cause bus storms. Bus storms are when a volatile variable changes and bus sniffing invalidated that variable in other memory. If volatile is high, invalidated interactions can lead to spikes in bus bandwidth. Therefore, use of volatile should be moderate.