For singletons with double lock, why volatile?
Prerequisite: The process of creating objects
To understand the creation of an object, you need to analyze the runtime data area, starting with an in-depth understanding of the LAYOUT of the JVM runtime data area and the behavior of the various stages of the class loading process. See the detailed process for analyzing new objects from a JVM perspective in the follow-up article.
This article doesn’t need to delve into either topic, just understand the overall process of creating objects.
Core: non – atomic operation, instruction reordering
private static volatile Singleton instance;
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) { instance = new Singleton(); }}}return instance;
}
Copy the code
purpose
Avoid reordering problems that cause other threads to see an uninitialized object that has been allocated memory and address (the object is still not available) and reference it (raising an exception).
Reorder analysis of instructions to create objects
The following code does not operate atomically in a multithreaded environment
instance = new Singleton();
Copy the code
This instruction pit is reordered in the following two possible instruction ordering scenarios.
Scenario 1: Not reordering (Normal step)
The normal low-level order of execution would be three steps:
Call the constructor of instance to initialize a member variable. 3. Reference instance on the stack to the packaged object in steps 1 and 2Copy the code
No matter where thread A is currently at step 1, 2, or 3, thread B may see only two states of instance: NULL and non-NULL.
The instance object in steps 1 and 2 is null, and the instance object seen in step 3 is non-NULL, which is fine for normal order.
Scenario 2: Reorder
If thread A is reordering, instance may become 1, 3, and 2. If thread A reaches step 3, instance is not null, but has not been initialized and is not available.
Thread B calls the getInstance singleton method double Check. When the first null check occurs, the instance reference is already referred to the memory block by thread A. If it is not null, the instance is returned.
However, when a field of this object is used, an exception occurs because it has not been initialized and is not available.
Solution: Use volatile to modify variables and disallow instruction reordering
The principle of volatile
Using volatile to modify member variables creates a memory barrier during assignment, meaning that the value of instance cannot be seen by other threads until 123 steps have been performed. This eliminates the problem of incomplete object state.
At the same time, volatile forces changes in the cache to be flushed to main memory, ensuring visibility to other threads.
At this point, invalidate another CPU’s cache row. When other cpus need to use variables from this cache row, they will go back to main memory to read the data and ensure that the data is up to date.