1. Introduction
To limit arbitrary creation of such objects, you need to keep the class constructor private, so that external classes cannot create objects of this type, and provide a global access point to the client object to facilitate its use of the singleton.
2. Re-understand the singleton pattern
2.1 What is the Singleton Pattern
Let’s start by defining a singleton: in the current process, classes created through the singleton pattern have one and only one instance.
Regardless of the CPU’s cache, the essence of a singleton is that only one object can exist in the allocated memory of a process, and the singleton pattern is a means or method to make that object unique in memory.
At the Java level, instances of objects are allocated in the heap, and what we need to ensure is that the object created by Class is globally unique in the heap memory region.
2.2 Why is the singleton pattern needed
So why do we need to use the singleton pattern? Obviously, the singleton pattern ensures global uniqueness in memory, avoiding repeated creation of object instances and saving system resources. However, its disadvantages are obvious. There is no interface, it cannot be inherited, and it also violates the principle of single responsibility (a singleton often uses more business scenarios. Just imagine, if a singleton is responsible for a function, its utilization rate of system resources is bound to decrease).
Singletons have the following characteristics:
- In Java applications, the singleton pattern ensures that only one instance of an object exists within a JVM
- The constructor must be private, and an external class cannot create the instance by calling the constructor method
- Without a set method exposed, an external class cannot call the set method to create the instance
- Provide a public get method to get the unique instance
So what’s the benefit of the singleton pattern?
- Some classes are created frequently, which can be a lot of overhead for large objects
- The new operator is omitted, reducing the frequency of system memory usage and reducing GC pressure
- Some classes in the system, such as the Controller in Spring, control the processing flow, and if more than one class can be created, the system is completely out of order
- The repeated occupation of resources is avoided
2. Singleton pattern implementation
2.1 Hungry and Hungry Mode
The implementation code is as follows:
package com.wxw.singleton; /** * @author: public id * @desc: 1. Hungry Man mode * @create: [1] public class HungrySingleton {// public class HungrySingleton {// public class HungrySingleton {// public class HungrySingleton Private static final HungrySingleton instance = new HungrySingleton(); [2] privatize the constructor, Private HungrySingleton() {} // [3] Public static HungrySingleton getInstance() {return instance; }}Copy the code
The hunger-han model is explained as follows:
- Private controls access to the object, final means that the object cannot be modified once it is created, static takes advantage of the JVM class loading mechanism, the object is created during the initialization phase of the class load, and the instance variable is stored in the method area (meta space).
- Privatize the constructor so that external classes cannot be retrieved by new.
- For the singleton pattern to be used, it needs to provide a public interface to the outside world
It is based on the Classloder mechanism to avoid multithreading synchronization problem, no lock, execution efficiency will be improved. However, the disadvantage of hangry is also obvious. Even if we do not use the object, we will initialize the instance when the class loads, which wastes memory.
2.2 Slacker mode
Said earlier the hungry type of defect is the memory resources waste, so is there a mechanism, can achieve lazy initialization, only when we need to be created.
Yes, let’s start with a thread-unsafe implementation of lazy singletons:
package com.wxw.singleton; /** * @author: public id * @desc: 2. */ public class LazySingleton {private static LazySingleton instance; Private LazySingleton() {} public static LazySingleton getInstance() {if (instance == null) {----> 2 instance = new LazySingleton(); // ----> comment 3} return instance; }}Copy the code
Lazy singletons implement lazy initialization, creating such an object instance only on getInstance(). But this is a thread-unsafe singleton, in the case of multi-threaded concurrency, there will be data synchronization problem.
Hungry Man mode scenario
In many e-market scenarios, if the data is frequently accessed and hot, I can use hung-hander mode to pre-load the data when the system starts up (similar to the warm-up of the cache) so that even the first user calls have no creation overhead, and the frequent calls have no memory waste.
Slacker mode scenario
And lazy we can use it for things that aren’t very hot, like data where you’re not sure if someone’s going to call the instance for a long time, so lazy, if you’re using hungry, but no one’s calling it after a few months, pre-loaded classes are going to waste resources in memory.
Thread safety
The lazy thread is not locked, everyone must know the lazy thread safety problem?
At run time, it is possible for multiple threads to call the getInstance method to get an instance of the Singleton, and it is possible for instance to be null when the first thread executes if (instance== NULL).
When instance=new Singleton() is not executed, the second thread enters if(instance==null). Because instance=new Singleton() has not been executed in the thread that entered the statement before, it will execute instance=new Singleton() to instantiate the Singleton object, Because the second thread also enters the if statement, it instantiates the Singleton object.
This results in two Singleton objects being instantiated. lock
package com.wxw.singleton; /** * @author public id: Java * @desc: * @date: 2021/7/22 */ public class ThreadSafeLazySingleton { private static ThreadSafeLazySingleton instance; // Private constructor, Private synchronized ThreadSafeLazySingleton() {} Public static synchronized ThreadSafeLazySingleton getInstance() { if (instance == null) { instance = new ThreadSafeLazySingleton(); } return instance; }}Copy the code
This is a typical time for space approach, regardless of the number of times, each time the instance is created, the first lock, then judge, seriously slow down the system processing speed.
Is there a better way to handle it? Singletons can be implemented using the dual detection mechanism (DCL)
2.3 Double Check Lock
package com.wxw.singleton; /** * @author: public id: Java * @desc: dual detection DCL * @create: [1] public class DoubleCheckSingleton {// public class DoubleCheckSingleton {// private volatile static DoubleCheckSingleton singleton = null; Private DoubleCheckSingleton() {} public static DoubleCheckSingleton getInstance() {// If not to enter the synchronized block below the if (singleton = = null) {/ / [3] thread-safe instance creation synchronized (DoubleCheckSingleton. Class) {[4] again check whether instance, Singleton = new singleton (); if (singleton == null) {// [5] } } } return singleton; }Copy the code
With the following questions, continue to see?
- Why volatile?
- Why double Check?
- Why add an if judgment around the sync block?
- Why add another layer of if judgment to the synchronized block?
- What is instruction reorder?
- Why volatile?
- Prevents instruction reordering because instance = new Singleton() is not an atomic operation
- Keep memory visible
Let’s take a look at this line: instance = new DoubleCheckSingleton(), which can be divided into three steps:
- Step1: allocate a space in memory.
- Step2: initialize the memory space.
- Step3: point the object in memory to instance.
Volatile is not necessary if the CPU or JIT compiler can follow the chip’s normal instructions, but CPU and JIT just-in-time compilers often reorder bytecode instructions for performance gains. This causes step2 and step3 to be executed in reverse order. The execution steps become:
- Step1: allocate a space in memory.
- Step3: point the object in memory to instance.
- Step2: initialize the memory space.
Example analysis
Now assume that there are two threads T1 and T2, and the T1 thread executes step3 after reordering, and the CPU is given to T2. At this point, instance is no longer null; it points to an address in memory. When T2 executes the first if, it finds that instance is not null and returns directly. However, this instance is not initialized, which will lead to unexpected errors in T2 execution.
Volatile variables are not cached locally by threads, and all reads and writes to the object are synchronized to main memory, ensuring that the object is accurate across threads
section
In DCl singleton, instance = new DoubleCheckSingleton() is divided into three steps: 1, allocate memory space, 2, initialize the object, 3, set instance pointing to the allocated address. A reordering of instructions, however, may optimize the order of instructions 1, 3, and 2. If it is a single thread access, there is no problem. If (instance==null) {if(instance==null) {if(instance==null) {if(instance==null) Cause another thread to directly get an uninitialized instance, which may cause the program to fail.
Therefore, use volatile to modify instance to prevent instruction reordering and ensure that the program runs properly.
- Why double Check?
Why use double Check? Suppose there are two T1’s and T2’s. T1 runs to comment 2 and the CPU is robbed by T2, which creates an object instance and releases the Java class lock. At this point, T1 regains CPU execution and acquires the class lock. Without a second if, T1 creates a new instance object, breaking the singleton.
So why is there a second if? Now why is there a first if? Now let’s say we have a thread T3, and if we don’t have the first if, it just tries to get the lock resource. Locking resources is very valuable, and if every thread requests locking resources directly as it comes in, rather than judging instance first, it will affect the performance of the application.
2.4 Static inner Classes
The essence of a static inner class is to take advantage of the JVM’s loading mechanism, which is essentially the same as hangry. Let’s look at the specific code implementation:
package com.wxw.singleton; /** * @author: public id: Java * @desc: singleton of static inner class * @create: Private InnerClassSingleton() {} private InnerClassSingleton() {} private InnerClassSingleton() { static InnerClassSingleton getInstance() { return SingletonFactory.INSTANCE; Private static Final class SingletonFactory {private static final InnerClassSingleton INSTANCE = new InnerClassSingleton(); }}Copy the code
As a private static inner class, it closes the interface for external instantiation, while inside it, it instantiates an object of an external class. So when does the InnerClassSingleton get instantiated? It is initialized only when it calls getInstance(). Is this lazy initialization?
2.5 Enumerating singletons
Having written the above four singletons, now let’s think more deeply. Is the singletons above really safe? So is there really no way to break their singletons? Here I use Double Check to verify the specification, to see the specific code implementation:
Reflection destruction singleton
The constructor of a singleton class is obtained by reflection. Since the constructor is private, setAccessible(true) indicates that the reflected object should be used with Java language access checks removed so that private constructors can be accessed, invalidating the singleton pattern.
@SneakyThrows private static void test_reflect() { DoubleCheckSingleton checkSingleton01 = DoubleCheckSingleton.getInstance(); Constructor<DoubleCheckSingleton> constructor = DoubleCheckSingleton.class.getDeclaredConstructor(); Since the constructor is private, setAccessible(true) indicates that the reflected object should be used without Java language access checks, allowing private constructors to be accessed. So that the singleton pattern failure constructor. SetAccessible (true); DoubleCheckSingleton checkSingleton02 = constructor.newInstance(); System.out.println(checkSingleton01.hashCode()); // 1625635731 System.out.println(checkSingleton02.hashCode()); / / 1580066828}Copy the code
If you want to defend against this attack, prevent the constructor from being called successfully twice. You need to count the number of instantiations in the constructor and throw an exception if it is greater than one.
package com.wxw.singleton.reflect; import lombok.SneakyThrows; import java.lang.reflect.Constructor; /** * @author: public id: Java * @desc: dual detection DCL * @create: 2019-10-20-20:47 */ public class DoubleCheckSingletonByReflect { private volatile static DoubleCheckSingletonByReflect singleton; private static int count = 0; / / statistics to create instances of the number of private DoubleCheckSingletonByReflect () {synchronized (DoubleCheckSingletonByReflect. Class) {if (count > 0) {throw new RuntimeException(" two instances created "); } count++; } } public static DoubleCheckSingletonByReflect getInstance() { if (singleton == null) { synchronized (DoubleCheckSingletonByReflect.class) { if (singleton == null) { singleton = new DoubleCheckSingletonByReflect(); } } } return singleton; } @SneakyThrows public static void main(String[] args) { Constructor<DoubleCheckSingletonByReflect> declaredConstructor = DoubleCheckSingletonByReflect.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); DoubleCheckSingletonByReflect s1 = declaredConstructor.newInstance(); DoubleCheckSingletonByReflect s2 = declaredConstructor.newInstance(); }}Copy the code
Deserialization breaks singletons
/** * implements Serializable */ @sneakythrows private static void test_serialize() {// serialize DoubleCheckSingleton checkSingleton01 = DoubleCheckSingleton.getInstance(); FileOutputStream fileOutputStream = new FileOutputStream("single"); ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream); oos.writeObject(checkSingleton01); oos.flush(); oos.close(); // 1. FileInputStream = new FileInputStream("single"); ObjectInputStream oosInput = new ObjectInputStream(inputStream); DoubleCheckSingleton checkSingleton02 = (DoubleCheckSingleton)oosInput.readObject(); Println ("checkSingleton01 = "+ checkSingleton01); // Println ("checkSingleton01 =" + checkSingleton01); System.out.println("checkSingleton02 = " + checkSingleton02); System.out.println(checkSingleton02 == checkSingleton01); }Copy the code
Pay attention to
When testing the deserialization destructible singleton, the object bean needs to be serialized first, assuming that the bean implements the Serializable interface
solution
In the singleton implementation class, implement the readResolve method
/** * private Object readResolve() {return singleton; }Copy the code
By checking double Check, we know that it can get object instances through reflection, which means that this is not a secure singleton either. So are there any secure singletons that prevent serialization and reflection?
Yes, you can do this by enumeration!
Finally, let’s look at the implementation of enumeration singletons, as recommended by the authors of the book Effective Java:
@date: 2021/7/20 */ public enum enumleton {INSTANCE; /** * public enum enumleton {INSTANCE; public static EnumSingleton getInstance() { return INSTANCE; }}Copy the code
Why can’t serialization and reflection break singletons of enumerated types?
Any Enum class inherits the abstract Enum class.
/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
Copy the code
Similarly, enumeration types are evaluated during reflection, and if they are enumerated types, an exception is thrown directly. This seems to solve our problem above by throwing an exception directly to prevent reflection and serialized broken enumeration singletons.
But that’s not all. Why doesn’t the JVM support reflection and serialization of enumerations instead of throwing exceptions to prevent them? What is the nature of enumerated classes, it can be guessed, since they are not supported, meaning that enumerated classes must be different from other classes?
Bytecode instructions can be obtained by using the Javap command. The bytecode instructions are as follows:
mac@wxw singleton % javap -c EnumSingleton.class Compiled from "EnumSingleton.java" public final class Com. WXW. Singleton. EnumSingleton extends # # descriptions of enumeration singleton Java lang. Enum < com. WXW. Singleton. EnumSingleton > {# # descriptions of enumeration variable public static final com.wxw.singleton.EnumSingleton INSTANCE; public static com.wxw.singleton.EnumSingleton[] values(); Code: 0: getstatic #1 // Field $VALUES:[Lcom/wxw/singleton/EnumSingleton; 3: invokevirtual #2 // Method "[Lcom/wxw/singleton/EnumSingleton;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[Lcom/wxw/singleton/EnumSingleton;" 9: areturn public static com.wxw.singleton.EnumSingleton valueOf(java.lang.String); Code: 0: ldc #4 // class com/wxw/singleton/EnumSingleton 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class com/wxw/singleton/EnumSingleton 9: areturn public static com.wxw.singleton.EnumSingleton getInstance(); Code: 0: getstatic #7 // Field INSTANCE:Lcom/wxw/singleton/EnumSingleton; 3: areturn static {}; Code: 0: new #4 // class com/wxw/singleton/EnumSingleton 3: dup 4: ldc #8 // String INSTANCE 6: iconst_0 7: invokespecial #9 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #7 // Field INSTANCE:Lcom/wxw/singleton/EnumSingleton; 13: iconst_1 14: anewarray #4 // class com/wxw/singleton/EnumSingleton 17: dup 18: iconst_0 19: getstatic #7 // Field INSTANCE:Lcom/wxw/singleton/EnumSingleton; 22: aastore 23: putstatic #1 // Field $VALUES:[Lcom/wxw/singleton/EnumSingleton; 26: return }Copy the code
An enumeration is essentially a class of final type whose variables are static and final.
Related articles
- Design pattern in-depth analysis —- singleton pattern
- Design Pattern series – singleton pattern