Static inner class
Private SingleInstanceTest() {} Public class SingleInstanceTest {// Private SingleInstanceTest() {} private static class InstanceHolder{ public static SingleInstanceTest INSTANCE = new SingleInstanceTest(); } // When an external class is loaded, it does not need to load the inner class immediately. Public static instancetest getInstanceStatic(){return instanceholder.instance; }}Copy the code
The JVM performs Class initialization during the initialization phase (that is, after a Class has been loaded and before it is used by a thread). During initialization, the JVM acquires a lock. This lock synchronizes the initialization of the same class by multiple threads.
The virtual machine ensures that a class’s Clinit () method is locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class’s () method, and all the other threads will block until the active thread finishes executing the Clinit () method. If there are lengthy operations in a class’s Clinit () method, multiple processes can block. (Note that other threads can block, but if clinit() is executed, other threads will not wake up and re-enter the Clinit () method. A type is initialized only once under the same loader.
Based on this feature, you can use this method to implement a thread-safe lazy initialization scheme. The advantage of this method is that the inner class needs to be loaded immediately when the outer class is loaded. If the inner class is not loaded, the INSTANCE will not be initialized and therefore will not occupy memory. This initialization scheme is also thread-safe
Double-checked lock
Public class SingleInstanceTest {private SingleInstanceTest() {} public static volatile SingleInstanceTest singleInstanceTest; Public static SingleInstanceTest getInstance() {if (SingleInstanceTest == null) {synchronized (SingleInstanceTest. Class) {// Check if (SingleInstanceTest == null) {// Allocate memory = allocate() // initialize objects SingleInstanceTest = new singleInstanceTest (); singleInstanceTest = new singleInstanceTest (); } } } return singleInstanceTest; }}Copy the code
There are a few things to note about this approach:
- 1. Purpose of the first null-out: If instance is not null in the first check, there is no need to perform the following locking and initialization operations, thus greatly reducing the performance overhead caused by synchronized
- 2. The purpose of the second nullation: Imagine a situation where thread 1 and thread 2 decide that the first nullation is null and the lock is blocked. If there is no second nullation, thread 2 will execute again after thread 1 completes, thus initializing twice. After two short calls, DCL is much safer and generally not a problem. However, there is still a risk when the amount of concurrency is extremely high. That is, volatile is working.
- 3. What Volatile does:
singleInstanceTest = new SingleInstanceTest();
This line of code can be decomposed into - 1. Allocate memory = allocate()
- Initialize ctorInstance(memory)
- 3. Point singleInstanceTest to the newly allocated memory address
These three steps, the second and third step may be reordering, this reorder without altering the single-threaded program execution results can improve the efficiency of the execution of the program, but in a multi-threaded cases, if the first implement the third step, could perform the second step, the other a concurrent program B in judging when empty, Instance is assumed not to be NULL, causing B to access an uninitialized object. The purpose of volatile is to ensure that program execution is ordered. Avoid the above situation.