Creation: Singleton Design Pattern 2
Catalog introduction
- 01. How to implement a singleton
- 02. Hungry Chinese-style implementation
- 03. Lazy-style implementation
- 04. Double DCL check mode
- 05. Static inner class method
- 06. Enumeration method singleton
- 07. The container implements the singleton pattern
01. How to implement a singleton
There are already many articles on how to implement a singleton pattern, but in order to ensure the integrity of the content, here is a brief introduction to several classic implementations. To sum up, to implement a singleton, we need to pay attention to the following points:
- The constructor needs to have private access, so as to avoid creating an instance externally through new;
- Consider thread safety issues when creating objects;
- Consider whether to support lazy loading;
- Consider whether the performance of getInstance() is high (locked or not).
02. Hungry Chinese-style implementation
Hungry-style implementation is relatively simple. When the class is loaded, the instance static instance has been created and initialized, so the instance instance creation process is thread-safe. However, such an implementation does not support lazy loading, as we can see from the name. The specific code implementation is as follows:
//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton { //static修饰的静态变量在内存中一旦创建,便永久存在 private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
code analysis
Hungry-style has created a static object for the system to use when the class is created, and will not change it in the future, so it is inherently thread-safe. where instance = new Singleton() can be written as:
static { instance = new Singleton(); }
Some people think this way of implementation is not good
- Because lazy loading is not supported, if the instance occupies a lot of resources (such as a lot of memory) or takes a long time to initialize (such as the need to load various configuration files), it is a waste of resources to initialize the instance in advance. The best way is to initialize it when it is used.
- However, I personally do not share this view. If the initialization takes a long time, then we'd better not wait until we actually use it before executing this time-consuming initialization process, which will affect the performance of the system (for example, when responding to client interface requests, do this initialization operation, it will cause the response time of this request to become longer, or even timeout). Using the Hungry-style implementation method, the time-consuming initialization operation is completed in advance when the program is started, so as to avoid performance problems caused by de-initialization when the program is running.
- If the instance occupies a lot of resources, according to the design principle of fail-fast (problems are exposed as soon as possible), then we also hope to initialize the instance when the program starts. If the resources are not enough, an error will be triggered when the program starts (such as PermGen Space OOM in Java), and we can fix it immediately. This can also prevent the system from crashing and affecting the availability of the system because the initialization of the instance occupies too many resources suddenly after the program runs for a period of time.
03. Lazy-style implementation
There is the hungry man style, and correspondingly, there is the lazy man style. The advantage of lazy style over hungry style is that it supports lazy loading. The specific code implementation is as follows:
//懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton { //私有的构造函数 private Singleton() {} //私有的静态变量 private static Singleton single=null; //暴露的公有静态方法 public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
code analysis
- The lazy (thread-unsafe) singleton pattern is divided into three parts: private constructors, private global static variables, and public static methods.
- What plays an important role is the static modifier static keyword. We know that in the program, any variable or code is automatically allocated memory by the system at compile time to store, and the so-called static means that the memory allocated after compilation will be stored. It always exists, and this space will not be released until the program exits the memory, so it is guaranteed that once the instance of the singleton class is created, it will not be reclaimed by the system unless it is manually set to null.
Advantages and disadvantages
- Advantages: lazy loading (load when needed)
- Disadvantages: Thread is not safe, and it is easy to be out of synchronization in multi-threading, such as frequent read and write operations on database objects.
The above can be seen to be thread-unsafe, in fact, it can be evolved:
public class Singleton { //私有的静态变量 private static Singleton instance; //私有的构造方法 private Singleton (){}; //公有的同步静态方法 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
code analysis
- The synchronized keyword is added to the getInstance() method of this singleton implementation, which tells Java (JVM) that getInstance is a synchronized method.
- Synchronization means that when two concurrent threads access the synchronized synchronization method in the same class, only one thread can be executed at a time, and the other thread must wait for the current thread to finish executing, so the synchronization method makes thread safe , which ensures that there is only one instance of a singleton.
Advantages and disadvantages
- Advantage: solves the problem of thread insecurity.
- Disadvantages: The efficiency is a bit low, and the synchronization lock must be judged every time an instance is called. Its disadvantage is that every call to getInstance() is synchronized, causing unnecessary synchronization overhead. This mode is generally not recommended.
However, the shortcomings of the lazy man are also obvious.
- A large lock (synchronzed) is added to the getInstance() method, resulting in very low concurrency of this function. If quantified, the concurrency is 1, which is equivalent to serial operation. And this function is always called during the use of the singleton. This implementation is acceptable if the singleton class is occasionally used. However, if it is used frequently, problems such as frequent locking, unlocking and low concurrency will lead to performance bottlenecks, and this implementation is not desirable.
04. Double DCL check mode
- Hungry style does not support lazy loading, lazy style has performance problems and does not support high concurrency. Then let's look at a singleton implementation that supports both lazy loading and high concurrency, that is, the double detection implementation.
In this implementation, as long as the instance is created, even if the getInstance() function is called again, it will not enter the locking logic again. Therefore, this implementation solves the problem of low concurrency of lazy people. The specific code implementation is as follows:
public class Singleton { private static Singleton singleton; //静态变量 private Singleton (){} //私有构造函数 public static Singleton getInstance() { if (singleton == null) { //第一层校验 synchronized (Singleton.class) { if (singleton == null) { //第二层校验 singleton = new Singleton(); } } } return singleton; } }
code analysis
- The highlight of this mode lies in the getInstance() method, in which the singleton is judged twice whether it is empty. The first level of judgment is to avoid unnecessary synchronization, and the second level of judgment is to create an instance in the case of null .
Advantages and disadvantages
- Advantages: It may be possible to run the singleton mode perfectly when there is not much concurrency and low security.
- Disadvantages: There may be serious security risks in the compilation process of different platforms.
Simulation analysis
Suppose thread A executes the singleton = new Singleton(); statement, which looks like a code, but it is not an atomic operation. This code will eventually be compiled into multiple assembly instructions, which will roughly do three things:
- (a) Allocate memory to the instance of Singleton
- (b) Call the constructor of Singleton() to initialize member fields;
- (c) Point the singleton object to the allocated memory space (that is, the singleton is not empty);
- However, because the Java compiler allows the processor to execute out of order, and before jdk1.5, the write-back order of Cache, registers, and main memory in JMM (Java Memory Model: java memory model) stipulates that the above step b and step c are The order of execution is not guaranteed. That is to say, the execution order may be abc or acb, if it is the pointing order of the latter, and it is switched to thread B just after the execution of c is completed and b has not yet been executed. At this time, because the singleton is executed in thread A After step c, it is already non-empty, so thread B takes the singleton directly, and an error occurs when it is used again. This is the DCL failure problem.
- But after JDK1.5, the official gave the volatile keyword and changed the code defined by singleton to: private volatile static Singleton singleton; //Use the volatile keyword
Some people on the Internet say that there are some problems with this implementation.
- Because of the reordering of instructions, the singleton object may be new, and after it is assigned to the instance, it is used by another thread before it has time to initialize (execute the code logic in the constructor). To solve this problem, we need to add the volatile keyword to the instance member variable to prohibit instruction reordering. In fact, only very low versions of Java have this problem. The high version of Java we use now has solved this problem in the JDK internal implementation (the solution is very simple, as long as the object new operation and initialization operation are designed as atomic operations, reordering can be naturally prohibited).
05. Static inner class method
Let's look at a simpler implementation method than double checking, which is to use Java's static inner classes. It's a bit like Hungry, but it can do lazy loading.
public class Singleton { private Singleton (){} ;//私有的构造函数 public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } //定义的静态内部类 private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); //创建实例的地方 } }
Advantages and disadvantages
- Advantages: lazy loading, thread safety (mutual exclusion of classes in java), and reduced memory consumption
code analysis
- When the Singleton class is loaded for the first time, the INSTANCE is not initialized. Only the first call to the Singleton's getInstance() method will cause the INSTANCE to be initialized.
- Therefore, calling the getInstance() method for the first time will cause the virtual machine to load the SingletonHolder class, which not only ensures the uniqueness of the singleton object, but also delays the instantiation of the singleton.
06. Enumeration method singleton
Introduce one of the simplest implementation methods, based on the singleton implementation of enumeration types. This implementation ensures the thread safety of instance creation and the uniqueness of the instance through the characteristics of the Java enumeration type itself. The specific code is as follows:
public enum Singleton { //enum枚举类 INSTANCE; public void whateverMethod() { } }
code analysis
The biggest advantage of the enumeration singleton mode is that it is simple to write. Enumerations are the same as ordinary classes in java. They can not only have fields, but also have their own methods. The most important thing is that the default enumeration instance is thread-safe. And in any case, it's a singleton. Even during deserialization, the enumeration singleton does not regenerate a new instance. For other methods, the following methods must be added to ensure that no new objects are generated during deserialization.
private Object readResolve() throws ObjectStreamException{ return INSTANCE; }
07. The container implements the singleton pattern
This one is relatively rare, go directly to the code, as shown below:
public class SingletonManager { private static Map<String, Object> objMap = new HashMap<String,Object>();//使用HashMap作为缓存容器 private Singleton() { } public static void registerService(String key, Object instance) { if (!objMap.containsKey(key) ) { objMap.put(key, instance) ;//第一次是存入Map } } public static ObjectgetService(String key) { return objMap.get(key) ;//返回与key相对应的对象 } }
code analysis
- At the beginning of the program, a variety of singleton patterns are injected into a unified management class, and the corresponding type of object is obtained according to the key when used.
5. Summary of Singleton Pattern
- Summary: No matter what form the singleton pattern is implemented in, their core principle is to privatize the constructor and obtain a unique instance through a static public method. In the process of obtaining, thread safety must be guaranteed, and at the same time, preventing Deserialization causes instance objects to be regenerated.
- Generally speaking, there are five ways to write the singleton pattern: lazy, hungry, double-checked lock, static inner class, and enumeration. The above are all thread-safe implementations. The first method above is thread-unsafe and excluded.
Considering:
- It is recommended to use 4.4 DCL double check mode, 4.5 static inner class singleton mode, etc.
- In general, it’s fine to use the hungry Chinese style directly. If lazy initialization is explicitly required, you tend to use static inner classes. If it comes to deserialization to create objects, it will try to use enumeration to implement singletons.
- If the singleton object holds the Context, it is easy to cause memory leaks. At this time, it should be noted that the Context passed to the singleton object is preferably the Application Context
more content
- GitHub: https://github.com/yangchong211
- Blog: https://juejin.cn/user/1978776659695784
- Blog summary: https://github.com/yangchong211/YCBlogs
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。