Lazy single case
First, write a simple example of the lazy pattern
public class SimpleSingleton { private static SimpleSingleton singleton; private SimpleSingleton() { } public static SimpleSingleton getInstance() { if (singleton == null) { // 1. Judge whether it is empty singleton = new SimpleSingleton(); //2. Perform initialization } return singleton; } }
Lazy means I'm so lazy that I'll prepare when I want to eat
For a singleton, you should try to create a singleton object every time you use it.
The above singleton mode has some problems. The first thing you will think of is multithreading.
Because 1 and 2 are not atomic operations
If in the case of multi-threaded access, one of the threads executes step 2
If the initialization of the member variable is not completed, another thread will proceed to step 1 at this time to judge that the member variable is still empty and will execute the initialization of instance. In this way, multiple threads will perform initialization operations to obtain different singleton objects, which does not meet the requirements of singleton.
Now that you understand the principle, you can carry out the next test and borrow the Debug tool of IDEA
Block the first thread Thread0 and continue to execute other threads when instance is initialized
The final result is that the objects obtained for Thread0 and other threads are not the same
If there is a multithreading problem, the first method we think of is locking, as shown below
public static SimpleSingleton getInstance() { synchronized (SimpleSingleton.class) { if (singleton == null) { // 1. Judge whether it is empty singleton = new SimpleSingleton(); //2. Perform initialization } } return singleton; }
In this way, step 1 and step 2 become atomic operations, but also lead to the serialization of multithreading, which has a certain impact on the efficiency,
On this basis, another solution has emerged. Step 1 is advanced through Double Checking Locking(DCL), which is equivalent to that when other threads obtain instances after instance initialization, there will be no locking and unlocking overhead. After all, locks still affect performance.
After modification, the code is as follows
public static SimpleSingleton getInstance() { if (null == singleton) { synchronized (SimpleSingleton.class) { if (singleton == null) { // 1. Judge whether it is empty singleton = new SimpleSingleton(); //2. Perform initialization } } } return singleton; }
However, there will still be problems with the above code, so we need to go back to the operation performed at the bottom in step 2, which is divided into three operations:
(1) Allocate a block of memory
(2) Initializing member variables in memory
(3) Point the instance reference to memory
Because there will be reordering before operations (2) and (3), that is, execute instance to memory first and initialize member variables.
At this point, another thread may get an object that is not fully initialized.
If you directly access the member variables in the singleton object, an error may occur.
This causes a typical "constructor overflow" problem.
The solution is also relatively simple. Use volatile to modify instance. The code is as follows:
private volatile static SimpleSingleton singleton; private SimpleSingleton() { } public static SimpleSingleton getInstance() { if (null == singleton) { synchronized (SimpleSingleton.class) { if (singleton == null) { // 1. Judge whether it is empty singleton = new SimpleSingleton(); //2. Perform initialization } } } return singleton; }
In this way, the problem of multithreading safety is solved. As for the role of volatile keyword, there is much more to talk about. For the time being, we will only talk about single case related/
Of course, the lazy man's singleton mode is not used very much in actual projects. Let's analyze the hungry man's singleton mode.
Single case of hungry man
The meaning of a hungry man is that I've always been hungry, so I have to prepare all the food. I'll eat if I want;
public class HungrySingleton { private final static HungrySingleton instance = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return instance; } }
It seems that the hungry man is relatively simple. For the example, you can refer to the Runtime class in Java, which is a very typical hungry man mode
There are problems, serialization and deserialization problems
public class HungrySingleton implements Serializable { private final static HungrySingleton instance = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return instance; } public static void main(String[] args) throws IOException, ClassNotFoundException { HungrySingleton instance = HungrySingleton.getInstance(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singletonFile")); out.writeObject(instance); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("singletonFile")); HungrySingleton newInstance = (HungrySingleton) inputStream.readObject(); System.out.println(instance); System.out.println(newInstance); } }
Execution result: instance and newInstance are not the same object
This requires a careful reading of the deserialization source code. Through the code reading, you will find that the deserialization creates an object through the reflection method, resulting in that the object after deserialization is not the same as that before serialization
For detailed code reading, please refer to the link Singleton, serialization, and readResolve() methods - Nuggets
Solution code
public class HungrySingleton implements Serializable { private final static HungrySingleton instance = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return instance; } // Used to solve serialization and deserialization problems public Object readResolve() { return instance; } }
During deserialization, the readResolve method will be called to obtain the original object through reflection and replace it, so as to ensure that the objects before and after serialization are the same.
Since this problem occurs during deserialization, it is also possible that someone in the project will use reflection to create the object, which will cause another problem,
The simple solution is to prohibit the instance from being created through reflection
public class HungrySingleton implements Serializable { private final static HungrySingleton instance = new HungrySingleton(); private HungrySingleton() { if (instance != null) { throw new RuntimeException("Prohibit creation by reflection"); } } public static HungrySingleton getInstance() { return instance; } public Object readResolve() { return instance; } }
Of course, this method cannot be used for lazy mode, because lazy mode belongs to delayed creation. Throw errors when it cannot be created.
Static inline singleton
The hungry Han singleton also has the problem of delayed loading. For example, instance will be initialized with the project. It may not be used for a long time in the later project operation, but it is always resident in memory and wastes resources.
It can be solved through the method of Inner Class
public class StaticInnerClassSingleton { public static class InnerClass { static { System.out.println("inner class"); } private static StaticInnerClassSingleton staticInnerClassSingleton; } private StaticInnerClassSingleton() {} public static StaticInnerClassSingleton getInstance() { System.out.println("get instance"); return InnerClass.staticInnerClassSingleton; } public static void main(String[] args) { StaticInnerClassSingleton.getInstance(); } }
This ensures that the object is initialized in memory when it is needed.
Container single example
public class ContainerSingleton { private static HashMap map = new HashMap<String, Object>(); private ContainerSingleton() {} public static void putInstance(String key, Object o) { if (key != null && key.length() > 0 && o != null){ map.put(key,o); } } public static Object getInstance(String key) { return map.get(key); } }
Let's go directly to the code, which is also common in the project. We borrow the key value structure of map to ensure the uniqueness of singleton.
For specific examples, refer to the SingletonBeanRegistry interface in Spring
Enumeration singleton
The simplest and safest way to write
public enum EnumSingleton { INSTANCE { }; public static EnumSingleton getInstance() { return INSTANCE; } }
ThreadLocal singleton
Thread dependent singleton mode
public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal(){ @Override protected Object initialValue () { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton() {} public static ThreadLocalSingleton getInstance(){ return threadLocal.get(); } }
The {ErrorContext in Mybatis uses this singleton pattern
But pay attention to garbage collection
Original link: There are so many ways to write singleton mode (analysis of JAVA singleton mode)