volatile solves the problems of visibility and reordering
volatile is a lightweight synchronization mechanism provided by the JVM
1. Ensure visibility
Variables decorated with volatile can be immediately visible to all threads
According to JMM related knowledge, after adding volatile keyword to variables, when variables in main memory are modified, other threads copying variables in main memory will invalidate the previously copied variables and read the updated variables from main memory again.
2. Prohibit command rearrangement
In java, not all statements are atomic (not all statements written in one line will be executed atomically)
The JVM will perform some additional optimization on the code written, but the principle is that the execution result of the single threaded program will not be affected, that is, the final execution result is guaranteed to be the same.
Let's start with a single instance mode
public class Singleton { private static Singleton instance = null;//Multiple threads share an instance private Singleton() {} public static Singleton getInstance() { if (instance == null){ synchronized(Singleton.class){ if (instance == null) instance = new Singleton(); } } return instance; } }
ps: there is a problem with this code, otherwise the volatile keyword will not be summarized
instance = new Singleton();
The above line of code is not an atomic operation. The JVM roughly divides the execution into the following three steps:
- Allocate memory address and memory space
- Instantiate object
- Assign the object to 1 and allocate the memory
Because of the reordering, it may be 1, 3, 2. In the case of a single thread, no exception will occur, but in the case of multiple threads:
Thread X: execute to 3 (assigned, not null but not instantiated), meanwhile
Thread Y: execute to
if (instance == null){ } return instance;
If it is found that instance is not null, it will directly return the uninstantiated object. If it is used later, it will cause unknown errors
======================================================================================
So to avoid the above problems, we add volatile keyword to instance to prohibit JVM reordering
private volatile static Singleton instance = null;
In this way, the singleton mode is complete
Note: the problem solved here is to prohibit reordering, but volatile does not guarantee atomicity, so thread safety cannot be guaranteed
How volatile works?
Memory barrier
Insert a StoreStore barrier before volatile write operation
After volatile write operation, insert a StoreLoad barrier
Insert a LoadLoad barrier before volatile read operation
After volatile read operation, insert a LoadStore barrier
Note: volatile is not thread safe
For example, common examples of "missing addition":
public class TestVolatile_1 { public static volatile int num = 0; public static void main(String[] args) throws Exception { for (int i = 0; i <100; i++) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <20000; i++) { num++;//num + + is not an atomic operation } } }).start(); } Thread.sleep(3000);//Sleep for 3 seconds to ensure that all 100 threads created have been executed System.out.println(num); } }
num + + is executed by multiple threads at the same time, resulting in missing addition
You can use atomic classes in JUC to ensure thread safety
public class TestVolatile_2 { public static AtomicInteger num = new AtomicInteger(0); public static void main(String[] args) throws Exception { for (int i = 0; i <100; i++) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <20000; i++) { num.incrementAndGet() ;// Num self increment, functionally equivalent to int type num + + operation } } }).start(); } Thread.sleep(3000);//Sleep for 3 seconds to ensure that all 100 threads created have been executed System.out.println(num); } }
AtomicInteger implements CAS algorithm