Singleton Design Mode
Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern is creative and provides the best way to create objects.
This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, directly without instantiating its object.
1 Singleton pattern structure
The main roles of the singleton pattern are as follows:
- Singleton class. Classes that can only create one instance
- Access class. Use singleton class
Implementation of 2 Singleton Mode
There are two types of singleton design pattern classification:
Hungry Han Style: Class loading causes the single instance object to be created
Lazy: Class loading does not cause the single instance object to be created, but only the first time it is used
2.1. Hungry Han-Style 1 (Static Variable Style)
/** * Hungry Han Style * Static variable creates object of class */ public class Singleton { //Private construction methods private Singleton() {} private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }
Explain:
This declares a static variable of type Singleton at the member location and creates an object instance of the Singleton class. The instance object is created as the class is loaded. If the object is large enough and has not been used, it will waste memory.
2.2 Hungry Han-Style 2 (Static Code Block Style)
/** * Hungry Han Style * Create this type of object in a static block of code */ public class Singleton { //Private construction methods private Singleton() {} //Create objects of this class at member locations private static Singleton instance; static { instance = new Singleton(); } //Provide a static method to get the object public static Singleton getInstance() { return instance; } }
Explain:
This way static variables of type Singleton are declared at member locations, while objects are created in static blocks of code and for class loading. So basically the same way as the hungry Han style, no change of soup or medicine, of course, there is also a problem of memory waste in this way.
2.3 Lazy-Style 1 (Thread Insecure)
/** * Lazy-man style * Thread insecurity */ public class Singleton { //Private construction methods private Singleton() {} //Create objects of this class at member locations private static Singleton instance; public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
Description: From the code above, we can see that this method declares static variables of type Singleton at member locations without assigning objects, so when does it assign values? The Singleton class object is created only when the getInstance() method is called to get the object of the Singleton class, thus achieving the effect of lazy loading. However, in a multithreaded environment, threading security issues arise.
2.4 Lazy-Style 2 (Thread Safety)
/** * Lazy-man style * Thread Security */ public class Singleton { //Private construction methods private Singleton() {} //Create objects of this class at member locations private static Singleton instance; //Provide a static method to get the object public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
Description: This method also achieves the lazy loading effect, while resolving the thread security issues. However, the synchronized keyword is added to the getInstance() method, which results in a particularly ineffective execution. From the code above, we can see that the thread security issues occur only when instances are initialized, and once initialization is complete, they do not exist. Therefore, you can lock only when initializing instances, not the entire getInstance method.
2.5 Lazy-Mode 3 (Double Check Lock)
Let's talk about locking in lazy mode again. For the getInstance() method, the vast majority of operations are read operations, which are thread-safe, so we don't have to have locks on each thread to invoke this method. We need to adjust the timing of locking. This also gives rise to a new implementation pattern: the double-check lock pattern
/** * Double check mode */ public class Singleton { //Private construction methods private Singleton() {} private static Singleton instance; //Provide a static method to get the object public static Singleton getInstance() { //First judgment, if instance is not null, do not enter the lock phase, return to the instance directly if(instance == null) { synchronized (Singleton.class) { //Determine again if it is null after grabbing the lock if(instance == null) { instance = new Singleton(); } } } return instance; } }
Double Check Lock mode is a very good single implementation mode, which solves the problems of singleton, performance and thread security. The above double check lock mode looks perfect, but it is actually a problem. In the case of multiple threads, null pointer problem may occur. The reason for the problem is that JVM optimizes and reorders instructions when instantiating objects.
To solve the null pointer exception caused by double check lock mode, only volatile keyword is needed, which can guarantee visibility and orderliness.
The specific reason for requiring the volatile keyword is that in concurrent cases, if there is no volatile keyword, at instance = new Singleton(); Can be broken down into 3 lines of pseudocode
a. memory = allocate() //Allocate memory b. ctorInstanc(memory) //Initialize Object c. instance = memory //Set instance to point to the address just assigned
The code above may be reordered from a-b-c to a-c-b at compile time. The following problems can occur in multithreaded scenarios. When thread A executes instance = new Singleton(); if(instance == null) {is executed when the B thread enters to execute. Assuming that a reordering of instructions occurs during the execution of A at this time, that is, A and C are executed first, but B is not executed. Since the A thread executes C and causes the instance to point to an address, the B thread decides that the instance is not null and jumps directly to the return instance; and returns an uninitialized object.
/** * Double check mode */ public class Singleton { //Private construction methods private Singleton() {} private static volatile Singleton instance; //Provide a static method to get the object public static Singleton getInstance() { //The first judgment, if instance is not null, does not enter the lock phase, returns to the actual if(instance == null) { synchronized (Singleton.class) { //Judge again if it is empty after grabbing the lock if(instance == null) { instance = new Singleton(); } } } return instance; } }
Summary:
The double-checked lock mode after adding the volatile keyword is a good single-instance implementation mode that ensures thread safety and performance in a multithreaded situation.
2.6 Lazy-Style 4 (Static Internal Class Style)
Instances in static internal class singleton mode are created by internal classes, since the JVM will not load static internal classes during the process of loading external classes, only the properties/methods of internal classes will be loaded when called, and their static properties will be initialized. Static properties are modified by static to ensure that they are instantiated only once and the order of instantiation is strictly guaranteed.
/** * Static Internal Class Style */ public class Singleton { //Private construction methods private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //Provide a static method to get the object public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
Explain:
The first time the Singleton class is loaded, INSTANCE is not initialized, only the first time getInstance is called, the virtual machine loads SingletonHolder
INSTANCE is initialized to ensure not only thread safety but also uniqueness of the Singleton class.
Summary:
The static internal class singleton mode is an excellent singleton mode and is a common singleton mode in open source projects. Without any locks, it ensures security under multiple threads without any performance impact or waste of space.
2.7 Enumeration Method
Enumeration classes implementing the singleton mode is a highly recommended singleton implementation mode because the enumeration type is thread-safe and can only be loaded once. Designers take full advantage of this feature of enumeration to implement the singleton mode. The enumeration method is very simple, and the enumeration type is the only singleton implementation mode used that will not be destroyed.
/** * Enumeration Method */ public enum Singleton { INSTANCE; }
Description: The enumeration method belongs to the Hungry-Han style.
3 Problems
3.1 Problem Demo
Destroy Singleton Mode:
Enables the Singleton class defined above to create multiple objects, with the exception of enumeration. There are two ways, serialization and reflection.
-
Serialization Deserialization
Singleton class:
public class Singleton implements Serializable { //Private construction methods private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //Provide a static method to get the object public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
Test class:
import com.wk.singletonPattern.interference.Singleton; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Test { private static Singleton readObjectFromFile() throws Exception { //Create Object Input Stream Object ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\kai\\Desktop\\a.txt")); //First read Singleton object Singleton instance = (Singleton) ois.readObject(); return instance; } private static void writeObject2File() throws Exception { //Gets the object of the Singleton class Singleton instance = Singleton.getInstance(); //Create Object Output Stream ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\kai\\Desktop\\a.txt")); //Write the instance object out to a file oos.writeObject(instance); } @org.junit.jupiter.api.Test public void writeObject2FileTest(){ try { writeObject2File(); } catch (Exception e) { assertTrue(false); } } @org.junit.jupiter.api.Test public void isEqualTest() throws Exception { Singleton s1 = readObjectFromFile(); Singleton s2 = readObjectFromFile(); assertTrue(s1==s2); } }
The result of running the above code is false, indicating that serialization and deserialization have broken the singleton design pattern.
-
reflex
Singleton class:
public class Singleton { //Private construction methods private Singleton() {} private static volatile Singleton instance; //Provide a static method to get the object public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
Test class:
public class Test { public static void main(String[] args) throws Exception { //Gets the byte code object of the Singleton class Class clazz = Singleton.class; //Gets the private parameterized construction method object of the Singleton class Constructor constructor = clazz.getDeclaredConstructor(); //Cancel access check constructor.setAccessible(true); //Create object s1 of Singleton class Singleton s1 = (Singleton) constructor.newInstance(); //Create object s2 of Singleton class Singleton s2 = (Singleton) constructor.newInstance(); //Determine if two Singleton objects created by reflection are the same object System.out.println(s1 == s2); } }
The result of running the above code is false, indicating that serialization and deserialization have broken the singleton design pattern
Note: Enumeration does not cause these two problems.
3.2 Problem Solution
-
Solution to destroy singleton mode by serialization and deserialization
Add the readResolve() method to the Singleton class, which is called reflectively during deserialization, and return the value of the method if defined, or the new object if not defined.
Singleton class:
public class Singleton implements Serializable { //Private construction methods private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //Provide a static method to get the object public static Singleton getInstance() { return SingletonHolder.INSTANCE; } /** * The following is to resolve the serialization deserialization crack singleton pattern */ private Object readResolve() { return SingletonHolder.INSTANCE; } }
Source Parsing:
ObjectInputStream class
public final Object readObject() throws IOException, ClassNotFoundException{ ... // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false);//Focus on the readObject0 method ..... } private Object readObject0(boolean unshared) throws IOException { ... try { switch (tc) { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));//Focus on the readOrdinaryObject method ... } } finally { depth--; bin.setBlockDataMode(oldMode); } } private Object readOrdinaryObject(boolean unshared) throws IOException { ... //isInstantiable returns true, executes desc.newInstance(), creates a new singleton class through reflection, obj = desc.isInstantiable() ? desc.newInstance() : null; ... // The desc.hasReadResolveMethod() method executes with true after adding the readResolve method to the ingleton class if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { // Call the readResolve method in the Singleton class through reflection to assign the return value to the rep variable // This makes multiple calls to the readObject method in the ObjectInputStream class, which then calls the readResolve method we defined, so the same object is returned. Object rep = desc.invokeReadResolve(obj); ... } return obj; }
-
Solution to Reflective Solution for Solving Singletons
public class Singleton { //Private construction methods private Singleton() { /* Reflection cracking singleton mode requires adding code */ if(instance != null) { throw new RuntimeException(); } } private static volatile Singleton instance; //Provide a static method to get the object public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
Description: This is a better way to understand. When a construction method is invoked through reflection for creation, an exception is thrown directly. Do not run this operation.
4 JDK Source Parsing-Runtime Class
In jdk, the Runtime class is the single-case design pattern used.
4.1. See through the source code which singleton mode is used
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ... }
From the source code above, you can see that the Runtime class implements the singleton pattern in a vicious Han style (static attribute).
4.2. Use methods in Runtime classes
import java.io.IOException; import java.io.InputStream; public class RuntimeDemo { public static void main(String[] args) throws IOException { //Get Runtime Class Object Runtime runtime = Runtime.getRuntime(); //Returns the total amount of memory in the Java virtual machine. System.out.println(runtime.totalMemory()); //Returns the maximum amount of memory the Java virtual machine is trying to use. System.out.println(runtime.maxMemory()); //Creates a new process, executes the specified string command, and returns the process object Process process = runtime.exec("ipconfig"); //Get the result of the command execution, through the input stream InputStream inputStream = process.getInputStream(); byte[] arr = new byte[1024 * 1024 * 100]; int b = inputStream.read(arr); System.out.println(new String(arr, 0, b, "gbk")); } }