I have only written fixed singleton patterns before, without careful study. The step-by-step singleton pattern is best described in books. But is written with CPP, and is their own Java step by step implementation again.
Step1 ADAPTS to a single-threaded Singleton
public class Singleton {
private Singleton() {}
private static Singleton INSTANCE=null;
public static Singleton getInstance(){
if (INSTANCE==null) {
INSTANCE=new Singleton();
}
return INSTANCE;
}}Copy the code
By making the constructor private, we avoid the class being instantiated externally, so that the only instance of the Singleton within the same virtual machine scope can be accessed only through the getInstance() method. And our methods and member variables are static.
insufficient
This works fine if we use it in a single thread, but what if we use it in multiple threads? If two or more students run an if statement that determines whether an instance is null, then each thread will create an instance if the instance is not created. This is not going to satisfy our singleton requirement.
Step2 under multithreading Singleton
public class Singleton {
private Singleton() {}
private static Singleton INSTANCE=null;
public static synchronized Singleton getInstance(){
if (INSTANCE==null) {
INSTANCE=new Singleton();
}
return INSTANCE;
}}Copy the code
Now, if you’re not careful, you might say these two lines of code aren’t the same. No, we implemented a synchronous lock on the getInstance method. If more than one thread wants to create an instance at this point, since only one thread can acquire a synchronous lock at a time, subsequent threads need to wait while the first thread adds the lock. When the first thread realizes that an instance has not been created, it does so. The first thread releases the synchronization lock, and subsequent threads add the synchronization lock, so the instance is created by the first thread, and the second thread does not create it again.
insufficient
Although we implemented singletons in a multi-threaded environment, you will find that we try to add a lock every time we get an instance via getInstance, but locking is a time-consuming operation that should be avoided as much as possible.
Step3 avoid time consuming caused by locking
We only need to lock our instance when it is not yet created to prevent multiple threads from creating the instance at the same time. We should avoid locking our instance when it is already created.
public class Singleton {
private Singleton() {}
private static Singleton INSTANCE=null;
public static Singleton getInstance(){
if (INSTANCE==null) {
synchronized (Singleton.class){
if (INSTANCE==null) {
INSTANCE=new Singleton();
}
}
}
return INSTANCE;
}}Copy the code
We do a check before locking so that the lock is only needed if instance does not exist. In this way, our singleton is written perfectly, but the judgment of the if statement tends to increase the error rate of our code. We must not stop there because we are trying to improve ourselves. Let’s think of a better solution.
Solution 1 is recommended
public class Singleton {
private Singleton(a) {}
public static final Singleton getInstance(a){
return MyInstance.INSTANCE;
}
private static class MyInstance{
private static final Singleton INSTANCE=newSingleton(); }}Copy the code
By the way, there are several ways to solve the problem, which is what we often call the lazy model. The lazy pattern implements the singleton pattern loaded on demand. This singleton is instantiated only when we call the getInstance method.
Solution two is recommended
public class Singleton{
private Singleton(a) {}
private static final Singleton INSTANCE=new Singleton();
public static final Singleton getInstance(a){
returnINSTANCE; }}Copy the code
Once the class is loaded, the instantiation of the singleton is complete, ensuring that the required instance already exists by the time getInstance is loaded. The hungry mode is inherently thread-safe because it creates a static object for the system to use at the same time the class is created and will not change.