Singleton pattern is a common design pattern, and may be the least code in design pattern. But less doesn't mean that it's easy, easy to use, easy to use, and easy to use. Because it involves a lot of Java underlying knowledge such as class loading mechanism, Java memory model, volatile and so on.
brief introduction
The singleton pattern belongs to the creation pattern in the design pattern of 23. The definition is to ensure that a class has only one instance and to provide a global access point.
It has the following three characteristics:
- There can only be one instance
- You have to create your own unique instance
- Providing global access points
Basic Realization Ideas
Singletons require that classes only return references to the same object and must provide a static way to get the instance
Implementation can be achieved through the following two steps:
- Privatization construction methods, prevention of external instantiation, and access to unique instances only through static methods provided externally
- Provides a static method to obtain an instance of an object.
Seven Realizations of Singletons
1. hungry man
public class EagetSingleton { private static final EagetSingleton INSANCE = new EagetSingleton(); // Privatization constructors to prevent external instantiation private EagetSingleton() { } // Providing static external access methods public static EagetSingleton getInstance() { return INSANCE; } }
Advantages: Simple writing, instantiating static variables when classes are loaded, and avoiding thread concurrency.
Disadvantage: Objects are instantiated during class loading, resulting in waste of resources.
2. Hungry Chinese Style (Static Code Block)
public class StaticBlockSingleton { private static StaticBlockSingleton INSTANCE = null; static { try { INSTANCE = new StaticBlockSingleton(); } catch (Exception e) { } } // Privatization constructors to prevent external instantiation private StaticBlockSingleton() { } // Providing static external access methods public static StaticBlockSingleton getInstance() { return INSTANCE; } }
This method is basically the same as the above-mentioned implementation, but it only puts the process of class instantiation into the static code block to instantiate, and also executes the static code block in the class loading process. The advantages and disadvantages are basically the same, but it can do some additional operations such as exception handling in the process of class instantiation.
3. Slacker (thread insecurity)
public class LazySingleton { private static LazySingleton INSTANCE = null; // Privatization constructors to prevent external instantiation private LazySingleton() { } // Providing static external access methods public static LazySingleton getInstance() { if (null == INSTANCE) { -------- 1 INSTANCE = new LazySingleton(); ------2 } return INSTANCE; } }
Advantages: It realizes lazy loading and avoids waste of resources.
Disadvantages: Threads are not safe, when a thread is executed to one place in a multi-threaded situation, there is no time to execute another thread down to another, so that two threads execute two codes at the same time, destroying the singleton.
4. Slacker (lock)
public class LazySyncSingleton { private static LazySyncSingleton INSTANCE = null; // Privatization constructors to prevent external instantiation private LazySyncSingleton() { } // Inefficiency // Providing static external access methods public static synchronized LazySyncSingleton getInstance() { if (null == INSTANCE) { INSTANCE = new LazySyncSingleton(); } return INSTANCE; } }
Thread insecurity in 3 was solved, and synchronized method was used to lock getInstance() method to achieve synchronous access.
Advantages: Thread synchronization
Disadvantages: inefficiency, locking the whole object in this way, synchronous access is required for every access to getInstance(), which is very inefficient for multi-threaded concurrency. In fact, we only need to lock the object before it is instantiated, and there is no concurrency problem after it is instantiated.
5. Slacker (double lock)
public class DCheckSingleton { private static volatile DCheckSingleton INSTANCE = null; // Privatization constructors to prevent external instantiation private DCheckSingleton() { } // Providing static external access methods public static DCheckSingleton getInstance() { if (null == INSTANCE) { synchronized (DCheckSingleton.class) { if (null == INSTANCE) { INSTANCE = new DCheckSingleton(); } } } return INSTANCE; } }
It solves the problem of low efficiency in 4 concurrent cases.
Advantages: thread safety, delayed loading, high efficiency
Relevant knowledge points: 1: volatile keyword ensures visibility and orderliness of memory. What happens if you don't add volatile keywords? I know that INSTANCE = new DCheckSingleton() is used for object instantiation; instead of being executed in one step, this code is divided into three steps: (1) creating a reference pointer INSTANCE for object creation in stack memory (2) creating a space in heap memory to store the instantiated object new DCheckSingleton(); (3) pointing INSTANCE to heap memory address J, VM only guarantees the code. Execution results are correct, and execution order is not guaranteed (Java memory model knowledge points are not mentioned here, interested students can go to understand some of the underlying JVM implementation principles) so 1, 2, 3 three steps may also be 1, 3, 2, so we may get a semi-finished object.
2: Knowledge Points Involving Class Instance
3: Involving the Java memory model
4: Some execution optimization and instruction rearrangement related to JVM
6. Static classes
public class InnerSingleton { private InnerSingleton() { } public static InnerSingleton getInstance() { return InnerClassSingleton.INSTANCE; } private static class InnerClassSingleton{ private static final InnerSingleton INSTANCE = new InnerSingleton(); } }
This method is basically the same as the hungry-man implementation mechanism. It uses the class loading mechanism to ensure thread security. The only difference between the hungry-man and the hungry-man is that it implements the lazy loading mechanism. Only when the getInstance() method is called, the InnerClassSingleton class is instantiated.
Advantages: It avoids thread insecurity, delayed loading and high efficiency.
7. enumeration
public enum EnumsSingleton { INSTANCE; @SuppressWarnings("unused") private void method() { System.out.println("------- newInstance"); } }
Use the enumeration added in JDK 1.5 to implement the singleton mode. Not only can we avoid the problem of multithreaded synchronization, but also we can prevent deserialization from recreating new objects. It may be that enumeration was added only in JDK 1.5, so it is rarely written in actual project development.
To this example, several implementation methods and advantages and disadvantages of each method are briefly introduced, enumeration is small but there are many knowledge points in the design.
Advantage
- In the singleton pattern, there is only one instance of the activity singleton, and all instances of the singleton class get the same instance. This prevents other objects from instantiating themselves and ensures that all objects access one instance.
- The singleton pattern has certain scalability. Classes control the instantiation process by themselves. Classes have corresponding scalability in changing the instantiation process.
- Controlled access to a unique instance is provided.
- Since there is only one object in the system memory, it can save system resources. When objects need to be created and destroyed frequently, singleton mode can undoubtedly improve system performance.
- Allow a variable number of instances.
- Avoid multiple occupancies of shared resources
shortcoming
- Not applicable to changing objects. If the same type of object always changes in different use case scenarios, the singleton will cause errors in the data and will not be able to preserve each other's state.
- Since there is no abstraction layer in the singleton pattern, it is very difficult to extend the singleton class.
- The responsibility of the singleton class is too heavy, which violates the "single responsibility principle" to a certain extent.
- The abuse of singletons will bring some negative problems. For example, in order to save resources, designing database connection pool objects as singletons may lead to too many programs sharing connection pool objects and overflow of connection pool. If the instantiated objects are not used for a long time, the system will be considered garbage and recycled, which will lead to the loss of object state.
Usage scenarios
- Objects that need to be created and destroyed frequently;
- Objects that are too time-consuming or resource-consuming to create, but often used;
- Tool class object;
- Objects that frequently access databases or files.
Be careful
Finally, I would like to talk about how to prevent violence from destroying the singles. It mainly introduces two ways and how to prevent them.
1: Using Java reflection
EagerSingleton instance = EagerSingleton.getInstance(); Constructor instance2 = instance.getClass().getDeclaredConstructor(); instance2.setAccessible(true); EagerSingleton instance3 = (EagerSingleton) instance2.newInstance(); System.out.println("===" + instance); System.out.println("===" + instance3);
Using Java reflection can achieve the effect of explosive cracking of singletons, running results I am not interested in posted here, you can try instance and instance 3 is definitely not an object.
How to prevent this? In fact, it is also very simple Java Security provides us with ready-made methods. Simply check with the Security Manager in the private construct and the code is as follows.
// Private construction methods to prevent external instantiation private EagerSingleton() { SecurityManager sm = new SecurityManager(); sm.checkPermission(new ReflectPermission("Forbidden reflection")); }
2: The second way is to use Java serialization and deserialization.
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt")); out.writeObject(instance); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("1.txt")); EagerSingleton readObject = (EagerSingleton) in.readObject(); in.close(); System.out.println("==" + instance); System.out.println("==" + readObject);
How to prevent? Simply rewrite the reverse of readResolve().
private Object readResolve() { return EagerSingleton.instance; }
The two ways of violence cracking and prevention are all introduced. Interested comrades can try it. I have not posted the complete test code and operation results here.
Have you ever wondered that our little list has been introduced!!!