1. Hungry-style singleton class
}
private static Singleton instance = new Singleton();
private static Singleton getInstance(){
return instance;
}
}
Features: Hungry style instantiation in advance, there is no multi-threading problem in lazy style, but no matter whether we call getInstance() or not, there will be an instance in the memory
2. Internal class singleton class
}
private class SingletonHoledr(){
private static Singleton instance = new Singleton();
}
private static Singleton getInstance(){
return SingletonHoledr.instance;
}
}
Features: In the internal class, lazy loading is implemented. Only when we call getInstance() will a unique instance be created in the memory. It also solves the problem of multi-threading in the lazy style. The solution is to use Classloader characteristic.
3. Lazy Singleton Class
}
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
return instance = new Singleton();
}else{
return instance;
}
}
}
Features: In the lazy style, there are threads A and B. When thread A runs to line 8, it jumps to thread B. When B also runs to line 8, the instances of both threads are empty, so it will generate Two examples. The solution is to synchronize:
Synchronization is possible but not efficient:
}
private static Singleton instance;
public static synchronized Singleton getInstance(){
if(instance == null){
return instance = new Singleton();
}else{
return instance;
}
}
}
There will be no error in writing a program like this, because the entire getInstance is a whole "critical section", but the efficiency is very poor, because our purpose is actually only to lock when initializing the instance for the first time, and then get the When using instance, there is no need for thread synchronization at all.
So smart people came up with the following approach:
Double check lock writing method:
public static Singleton getSingle(){ //External objects can be obtained through this method
if(single == null){
synchronized (Singleton.class) { //It ensures that only one object can access this synchronized block at the same time
if(single == null){
single = new Singleton();
}
}
}
return single; //Return the created object
}
}
The idea is very simple, that is, we only need to synchronize (synchronize) the part of the code that initializes the instance so that the code is both correct and efficient.
This is the so-called "double check lock" mechanism (as the name suggests).
Unfortunately, this way of writing is wrong on many platforms and optimizing compilers.
The reason is that the behavior of the line of code instance = new Singleton() on different compilers is unpredictable. An optimizing compiler can legally implement instance = new Singleton() as follows:
1. instance = allocate memory to the new entity
2. Call the Singleton constructor to initialize the instance member variables
Now imagine that threads A and B are calling getInstance. Thread A enters first and is kicked out of the CPU when step 1 is executed. Then thread B enters, and what B sees is that instance is no longer null (memory has been allocated), so it starts to use instance with confidence, but this is wrong, because at this moment, the member variables of instance are still default value, A has not yet had time to execute step 2 to complete the initialization of the instance.
Of course, the compiler can also implement it like this:
1. temp = allocate memory
2. Call the constructor of temp
3. instance = temp
If the compiler behaves like this, we seem to have no problem, but the fact is not that simple, because we cannot know how a certain compiler does it, because this problem is not defined in Java's memory model.
Double check locking is applicable to basic types (such as int). Obviously, because the basic type does not call the constructor.