Singleton definition: ensure that the class has one and only one instance, and provide a global access point about it
Direct loading: globally unique and immutable from beginning to end
Load on demand: the singleton is empty before the external call. After the first external call, it is globally unique and immutable
1 direct loading
public class Singleton { // Private: private, which ensures encapsulation. The external can only be accessed through the get method, not directly // Static: static, which ensures that the member belongs to a class (not an object); If the static modifier is not written, it indicates that the member belongs to an object, which cannot be a singleton // final: the member is immutable private static final Singleton INSTANCE = new Singleton(); // The private constructor cannot be called externally, that is, the class cannot be instantiated by calling the constructor private Singleton() { } // Public static get method for external access to private instances public static Singleton getInstance() { return INSTANCE; } }
2.0 Load On Demand
public class Singleton { // Without final, INSTANCE has not been instantiated private static Singleton INSTANCE = null; private Singleton() { } // Re instantiation of external call public static Singleton getInstance() { if (INSTANCE == null) { // Problem: in multithreaded environment, INSTANCE is easy to be instantiated many times INSTANCE = new Singleton(); // Statement 1 } return INSTANCE; // Statement 2 } }
Why is INSTANCE easy to be instantiated multiple times in a multithreaded environment?
For example, assuming that INSTANCE has not been instantiated, thread A and thread B access the getInstance() method at the same time,
Step 1: thread A executes statement 1. At this time, thread A obtains an INSTANCE of INSTANCE, which is set to x, that is, INSTANCE==x
Step 2: thread B executes statement 1. At this time, thread B obtains another new INSTANCE of INSTANCE, which is set to y, that is, INSTANCE==y
Step 3: thread A executes statement 2, where INSTANCE==y
Question:
- Thread A should return x instead of being replaced
- INSTANCE is a single INSTANCE and should be globally unique. It cannot be x first and then y later
- The more threads, the more serious the problem
solve:
Only one initial thread can execute new Singleton() to generate an instance, and all threads refer to the instance
2.1 deferred loading is applicable to concurrent scenarios: synchronized method
public class Singleton { private static Singleton INSTANCE = null; private Singleton() { } // Problem: synchronized is a heavyweight lock. Locking the whole method (with a wide range of influence) will significantly reduce the concurrency performance public static synchronized Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return INSTANCE; } }
2.2 delayed loading to improve concurrency performance: double detection
public class Singleton { private static Singleton INSTANCE = null; private Singleton() { } // Double detection // Problem: JVM instruction rearrangement public static Singleton getInstance() { if (INSTANCE == null) { synchronized(Singleton.class) { if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } }
What is JVM instruction rearrangement?
new Singleton() divides JVM execution into three steps (three instructions):
memory = allocate(); // 1. Allocate the memory space of the object ctorInstance(memory); // 2. Initialization object instance = memory; // 3. Set instance to point to the memory address just allocated
The compiler optimizes the instruction execution order and changes the order. For example, the execution order of 2 and 3 above is interchanged:
memory = allocate(); // 1. Allocate the memory space of the object instance = memory; // 3. Set instance to point to the memory address just allocated ctorInstance(memory); // 2. Object initialization
Before initializing the object, point instance to the address assigned to the object and bring it into the previous singleton static method to observe:
public class Singleton { private static Singleton INSTANCE = null; private Singleton() { } public static Singleton getInstance() { if (INSTANCE == null) { // Statement 1 synchronized(Singleton.class) { if (INSTANCE == null) { // When new Singleton() is executed, it is subdivided into the following three steps /*memory = allocate(); instance = memory; ctorInstance(memory); */ INSTANCE = new Singleton(); // Statement 2 } } } return INSTANCE; } }
Thread A executes statement 2: instance = memory after execution; ctorInstance(memory) has not been executed;
At this time, thread B executes statement 1 and finds that INSTANCE is not empty, so it returns directly. At this time, INSTANCE is wrong
2.3 delay loading double detection and disable JVM instruction rearrangement: volatile keyword
public class Singleton { // volatile add here private static volatile Singleton INSTANCE = null; private Singleton() { } public static Singleton getInstance() { if (INSTANCE == null) { synchronized(Singleton.class) { if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } }
Summary: can I use direct loading or don't use lazy loading