The origin of the double check lock
In Java programs, it is sometimes necessary to postpone initialization of some high-overhead objects, and to initialize the object only when it is actually used, you need to defer the initialization technique.
The correct implementation of lazy initialization requires some skill, otherwise prone to problems, described below.
Scenario 1
public class unsafelazyinit{ private static Instance Instance; public static Instance getinstance () { if (Instance = ) {instance = new Instance (); return instance; } }
The mistake of this approach is obvious, if two threads call getinstance, because access to shared variables is not synchronized, it is easy to see the following two scenarios:
1. Threads A and B both see that the instance is not initialized, so they are initialized separately.
2.instance=new The instance operation is reordered, the actual execution may be: Allocate memory first, then assign to instance, and then initialize.
If this is the case, other threads may read the instance object that has not yet been initialized.
Scenario 2
Public class Privatestatic Instance Instance; Public Static synchronized Instance getinstance () { ifnull) { new Instance (); } return instance;
The problem with this approach is that it is clear that each read instance needs to be synchronized and may have a significant impact on performance.
Scenario 3
Scenario 3 is a false double detection lock implementation, see Code:
Public classunsafelazyinit{Private StaticInstance Instance; Public StaticInstance getinstance () {if(Instance = =NULL) {synchronized (unsafelazyinit.classs) {if(Instance = =NULL) {instance=NewInstance (); } } } returninstance; } }
This solution seems to solve the problems in both scenarios, but there are also problems.
Source of the problem
New Instance ();
This statement, in practice, may be split into three statements, as follows:
Memory =/ ///
According to the reordering rules, the latter two statements do not have data dependencies, so they can be reordered.
After reordering, it means that after the instance domain is assigned, the object pointed to may not have been initialized, and the instance domain is a static domain.
Can be read by other threads, then other threads can read to the instance domain that has not yet been initialized.
Volatile-based solutions
To solve this, you only need to suppress the reordering of statements 2 and 3, so you can use volatile to modify the instance.
Private volatile Static Instance Instance;
Because volatile semantics prevents the compiler from reordering operations that precede volatile writes to volatile.
Class-Based initialization solutions
The Java language Specification specifies that for each class or interface C, there is a unique initialization lock LC corresponding to it, from C to LC mapping, implemented by the JVM.
When each thread reads information from a class, if the class has not yet been initialized, it attempts to get the LC to initialize and waits for other threads to release the LC if the acquisition fails.
If the LC can be obtained, the initialization state of the class is determined, and if it is bit-initialized, it is initialized. If it is initializing,
Wait for other thread initialization to complete, and if it is already initialized, use such an object directly.
Public class instancefactory{ privatestaticclass instanceholder{ publicstaticnew Instance (); } Public Static Instance getinstance () { return// }
Conclusion
Field lazy initialization reduces the overhead of initializing a class or creating an instance, but increases the cost of a field that is delayed by 0 access.
Most of the time, normal initialization is better than lazy initialization. If you do need to use thread-safe deferred Initialization for instance fields,
Use the volatile-based deferred initialization scheme described above, and if you do need to use thread-safe deferred initialization for static fields,
Use the above deferred initialization based on the class initialization scheme.
JAVA double check locking and deferred initialization