The pattern is separated from the language, and the singleton pattern in the design pattern is very important in the concurrency. We should not indulge in the language and architecture, but think about the problem from the design point of view. Technology is the easiest to be replaced. Only by forming its own methodology and product thinking can we go further. Written before the beginning
How does the singleton pattern come into being?
Do multithreaded objects operate on different objects or the same object?
If you want to operate on the same object, you need to ensure the uniqueness of the object.
The problem to be solved by the singleton pattern is that it is instantiated only once during the instantiation process.
The general methods are: 1) providing an instantiation process, that is, the new method; 2) providing the getInstance method that returns the instance object.
1, Single case pattern classification
There are many kinds of classification, which are constantly evolving. I'll give you a list of the commonly used ones. In the future, if we can help you move bricks, it's worth it. And we analyze each classification from thread safety, performance and lazy loading.
1.1 hungry man mode
The instance object is generated when loading. The image becomes the hungry man mode, and the object will be generated immediately.
Code example:
public class HungerySingleton { //Instance object generated when loading private static HungerySingleton instance=new HungerySingleton(); private HungerySingleton(){ } //Return instance object public static HungerySingleton getInstance(){ return instance; } public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(()->{ System.out.println(HungerySingleton.getInstance()); }).start(); } } }
Thread safety: it is instantiated when loading, so it can only be instantiated once. Thread safety.
Performance: the performance is relatively good. The main impact is the memory impact. It will not occupy the memory for a long time.
Lazy load: there is no delayed load. If there are many member variables that are not used for a long time, they will always exist in memory, but this release will not result in memory overflow.
1.2 lazy mode
The starving mode is optimized, and delayed loading is adopted, which is only loaded into the memory when it is used.
Code example:
public class HoonSingleton { private static HoonSingleton instance=null; private HoonSingleton(){ } public static HoonSingleton getInstance(){ if(null==instance) instance=new HoonSingleton(); return instance; } public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(()->{ System.out.println(HoonSingleton.getInstance()); }).start(); } } }
Thread safety: it is not safe and cannot guarantee the uniqueness of the sample object. As shown in the figure, when two threads are judged to be null to instantiate, problems will occur. The figure below shows why threads are unsafe.
Lazy load: Yes
Performance: good
1.3 lazy mode + synchronization
The implementation of double check locking is as follows: example code:
public class DCL { private static DCL instance=null; private DCL(){ } public static DCL getInstance(){ if(null==instance) synchronized (DCL.class){ if(null==instance) instance=new DCL(); } return instance; } public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(()->{ System.out.println(DCL.getInstance()); }).start(); } } }
Thread safety: thread safety
Performance: quite good
Lazy load: Yes
But there's a problem with this model.
For example:
public class DCL { private static DCL instance=null; private DCL(){ //Call database connection, socket connection, instantiate object. The order is not guaranteed, //It is possible to rearrange the instructions, and rearrange the instance to the front. conn; socket; instance = new DCL(); //Possible command rearrangement: // instance = new DCL(); // conn; // socket; } }
It is easy to cause null pointer exception due to instruction rearrangement if it is the instantiation object for establishing socket or database connection. As shown in the figure below, when the first thread is instantiated and there is no real connection to the network or database, the second thread will call the connection directly. Because the instruction is rearranged, instance is not null, but conn and socket have not been established, which will cause the second null pointer exception. This is due to the rearrangement of instructions, so we have a concept in mind. I don't have to meet them, but at least when I meet them, I have a spectrum in mind.
volatile can be used to limit instruction rearrangement. For example, with this restriction, statements before instance will not be rearranged behind it.
private volatile static DCL instance=null;
1.4 Holder mode
When a class is declared, the instance variable is not declared in the member variable, but in the internal static class. Using static inner classes to ensure synchronization is the same as locking. During the initialization of HolerDemo, the instance object will not be created. The static inner class will be loaded only when it is called. If the instantiation is placed in the static inner class, it can be instantiated when it is in use, and lazy loading is realized.
This mode combines the starved mode and the lazy mode, and lazy loading does not need to be locked. At present, it is the most widely used way.
public class HolderDemo { private HolderDemo(){ } //Static inner classes are loaded only when called private static class Holder{ private static HolderDemo instance=new HolderDemo(); } //Lazy load public static HolderDemo getInstance(){ return Holder.instance; } }
Thread safety: thread safety, because static classes can only be instantiated once
Lazy load: Yes
Performance: good, no lock, no serialization, better performance.
The following way is more intelligent. You may not have seen it.
1.5 enumeration mode
This is a method strongly recommended by the author in Effective Java. Here is an example,
Enumerate the constants in it, and instantiate them only once when loading. In this way, we can only load once, but there is no lazy load. We introduce the idea of holder mode.
public enum EnumSingleton { INSTANCE; public static EnumSingleton getInstance(){ return INSTANCE; } }
Enumeration + Holder to implement lazy loading
You need to combine the above enumeration and holder to understand whether this is a bit silly. See how many times you are running.
Because constants in enumeration types can directly call methods and variables in enumeration. INSTANCE is the EnumHolder type. Instantiate only when called. Let's have a good understanding.
public class EnumSingletonDemo { private EnumSingletonDemo(){ } //Inner class enumeration //Lazy load, load only when called. private enum EnumHolder{ //Type is EnumHoler type INSTANCE; private EnumSingletonDemo instance=null; EnumHolder(){ instance=new EnumSingletonDemo(); } } //Lazy load public static EnumSingletonDemo getInstance(){ return EnumHolder.INSTANCE.instance; } }
The last two modes are the most widely used. You must understand them. Only in this way can the code be powerful.
I once worked as an intern in a startup company and saw a code written by a friend, very NB. Unfortunately, after working with him for several months, he left. His code is this style, very elegant. In the same sentence, architecture is new every year. It's easy to replace. The design idea and foundation are only the foundation of life.