What is volatile
Volatile is an important keyword in Java concurrent programming. It is likened to “lightweight synchronized”. Unlike synchronized, volatile only modifies variables, not methods and code blocks.
Here is the singleton pattern implemented using the volatile keyword:
public class Singleton implements Serializable { private static volatile Singleton singleton; private Singleton() {} public static Singleton getSingleton() { if (singleton==null) { // 1 synchronized (Singleton.class) { // 2 if (singleton==null) { // 3 singleton = new Singleton(); // 4 } } } return singleton; } private Object readResolve() {return singleton; }}Copy the code
1. Why do singletons use volatile?
First, understand what new Singleton() does. 1. Check whether the class object is loaded. If not, load, parse, and initialize the class. 2. The VM allocates memory space, initializes the instance, 3. Calls the constructor, 4. In order to optimize the program, the CPU may perform instruction reordering, resulting in instance memory being used before it is allocated.
Suppose there are two threads A and B, thread A executes to new Singleton() and starts to initialize the instance object. Due to instruction reordering, this new operation assigns the reference before executing the constructor. Thread B calls the new Singleton() method, finds that the reference is not null, and returns the reference address. Thread B then performs some operation, which may cause thread B to use a variable that has not yet been initialized.
2. What is the significance of steps 1, 2, 3, and 4 in the singleton pattern?
First, steps 2 and 3 are guaranteed singletons. Assuming that thread A and thread B both perform Step 2, thread A obtains the lock and performs Step 3. If no instance is created, thread A will execute new to create the instance. Then thread A releases the lock, thread B obtains the lock, and performs Step 3 to discover that the instance has been created. Locking consumes resources. Step 1 is to reduce resource consumption.
Second, the characteristics of volatile
1. Disable command reordering
Instruction reordering is the JVM’s attempt to optimize instructions and improve program efficiency by maximizing parallelism without affecting the execution results of a single-threaded program. Instruction reordering includes compiler reordering and runtime reordering.
The volatile keyword provides a way to prevent instructions from being reordered by inserting a memory barrier into the instruction sequence to prevent a particular type of handler from being reordered when the compiler generates a bytecode file.
JVM memory barrier insertion strategy:
Each volatile write is preceded by a StoreStore barrier, Store1; StoreStore; Store2. Before Store2 and subsequent write operations are executed, ensure that the write operations of Store1 are visible to other processors to ensure order and visibility.
Insert a StoreLoad barrier, Store1, after each volatile write; StoreLoad; Load2, before Load2 and subsequent read operations are executed, ensure that the write operation of Store1 is visible to other processors. It has the highest overhead and has the functions of the other three to ensure order and visibility.
Insert a LoadLoad barrier, Load1, after each volatile read; LoadLoad; Load2: Before Load2 and subsequent read operations, ensure that the data read by Load1 has been read.
Insert a LoadStore barrier, Load1, after each volatile read; LoadStore; Store2: Before Store2 and subsequent write operations, ensure that data read by Load1 has been read.
2. Ensure memory visibility
Visibility means that a read to a volatile variable can always obtain the last write to a volatile variable from any other thread. Visibility is implemented based on the memory semantics of volatile reads and writes:
- Memory semantics for volatile writes: When a volatile variable is written, the JVM flushs the value of the variable from the thread’s working memory to main memory.
- Memory semantics for volatile reads: When a volatile variable is read, the JVM first invalidates the variable in working memory and retrieves the latest valid value from main memory.
Three, use scenarios
(1) Volatile is a lightweight synchronization mechanism. Unlike synchronized, volatile guarantees order and visibility, not atomicity.
(2) Volatile does not modify variables whose writes depend on the current value. The volatile keyword does not work if the current value is related to the previous value of the variable, which means that expressions such as “count++” and “count = count+1” are not atomic operations.
(3) It is not necessary to use volatile when the variable to be accessed is already in a synchronized block or is a constant;
(4) Volatile ensures order and blocks out necessary code optimizations in the JVM, so it is inefficient and must be used only when necessary.
(5) Use volatile instead of synchronized in the following two scenarios:
-
The result of the operation is independent of the current value of the variable, or ensures that only a single thread will modify the value of the variable.
-
Variables do not need to participate in invariant constraints with other state variables.