4. Creation mode
The main focus of creative mode is "how to create objects?", Its main feature is "separating the creation and use of objects".
This can reduce the coupling degree of the system, and users do not need to pay attention to the creation details of objects.
The creation mode is divided into:
- Singleton mode
- Factory method model
- Abstract engineering pattern
- Prototype mode
- Builder pattern
4.1 single case design mode
Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern is a creation pattern, which provides the best way to create objects.
The singleton mode has three features:
① a singleton class has only one instance object;
② the singleton object must be created by the singleton class itself;
③ the singleton class provides a global access point for accessing the singleton.
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, which can be accessed directly without instantiating the object of this class.
4.1.1 structure of singleton mode
The main roles of singleton mode are as follows:
- Singleton class. Only one instance of a class can be created
- Access class. Using singleton classes
4.1.2 implementation of singleton mode
There are two types of singleton design patterns:
Hungry Chinese style: class loading will cause the single instance object to be created
Lazy: class loading does not cause the single instance object to be created, but only when the object is used for the first time
-
Hungry Han style - mode ① (static variable mode)
//Hungry Han style //Static variables create objects of classes public class Singleton { //Private construction method private Singleton() {} //Where the class member is created private static Singleton instance = new Singleton(); //Provide a static method to get the object public static Singleton getInstance() { return instance; } }
Test class:
public class Client { public static void main(String[] args) { //Create an object of the Singletion class Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); //Judge whether the two obtained objects are the same object System.out.println(instance == instance1); } }
explain:
This method declares the static variable of Singleton type at the member position, and creates the object instance of Singleton class. The instance object is created as the class loads. If the object is large enough and has not been used, it will cause a waste of memory.
-
Hungry Chinese style - mode ② (static code block mode)
//Hungry Han style //Create this type of object in a static code block public class Singleton { //Private construction method private Singleton() {} //Create an object of this class at the member location //Static attribute, static code block, who declares first, who loads first private static Singleton instance; static { instance = new Singleton(); } //Provide a static method to get the object public static Singleton getInstance() { return instance; } }
explain:
In this way, a static variable of Singleton type is declared at the member position, and the object is created in the static code block and for the loading of the class. Therefore, it is basically the same as the starving Chinese method ①. Of course, this method also has the problem of memory waste.
-
Lazy mode ① (thread unsafe)
//Lazy style //Thread unsafe public class Singleton { //Private construction method private Singleton() {} //Create an object of this class at the member location private static Singleton instance; //Provide a static method to get the object public static Singleton getInstance() { //Judge whether instance is null. If it is, it has not been created if(instance == null) { instance = new Singleton(); } return instance; } }
explain:
From the above code, we can see that this method declares a static variable of Singleton type at the member position, and does not assign an object. When did the assignment take place? The Singleton class object is created only when the getInstance() method is called to obtain the Singleton class object, which realizes the effect of lazy loading. However, if it is a multithreaded environment, thread safety problems will occur.
-
Lazy - mode ② (thread safety)
Analysis: when thread ① enters getInstance judgment, if thread ② grabs resources at this time, thread ② has no way to enter judgment due to synchronized.
//Lazy style //Thread safety public class Singleton { //Private construction method private Singleton() {} //Create an object of this class at the member location 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; } }
explain:
This method also realizes the effect of lazy loading and solves the problem of thread safety. However, the synchronized keyword is added to the getInstance() method, resulting in a particularly low execution effect of the method. From the above code, we can see that the thread safety problem only occurs when initializing instance. Once the initialization is completed, it does not exist.
-
Lazy - mode ③ (double check lock)
Let's talk about locking in lazy mode. For getInstance() method, most operations are read operations, which are thread safe. Therefore, we don't have to make each thread hold a lock to call this method. We need to adjust the timing of locking. This also produces a new implementation mode: double check lock mode
//Double check mode public class Singleton { //Private construction method private Singleton() {} private static Singleton instance; //Provide a static method to get the object public static Singleton getInstance() { //For the first time, if instance is not null, it will not enter the lock grabbing stage and return the instance directly if(instance == null) { synchronized (Singleton.class) { //After grabbing the lock, judge whether it is null again if(instance == null) { instance = new Singleton(); } } } return instance; } }
Double check lock mode is a very good singleton implementation mode, which solves the problems of singleton, performance and thread safety. The above double check lock mode looks perfect, but it is actually a problem. In the case of multithreading, null pointer problems may occur. The reason for the problem is that the JVM will optimize and reorder instructions when instantiating objects.
To solve the problem of null pointer exception caused by double check lock mode, you only need to use volatile keyword, which can ensure visibility and order.
//Double check mode public class Singleton { //Private construction method private Singleton() {} private static volatile Singleton instance; //Provide a static method to get the object public static Singleton getInstance() { //In the first judgment, if instance is not null, it will not enter the lock grabbing stage and return to the actual state directly if(instance == null) { synchronized (Singleton.class) { //After grabbing the lock, judge whether it is empty again if(instance == null) { instance = new Singleton(); } } } return instance; } }
Summary:
The double check lock mode after adding volatile keyword is a better singleton implementation mode, which can ensure thread safety and no performance problems in the case of multithreading.
-
Lazy - mode ④ (static internal mode)
In the static internal class singleton mode, the instance is created by the internal class. Because the JVM will not load the static internal class in the process of loading the external class, it will be loaded only when the properties / methods of the internal class are called, and its static properties will be initialized. Because static attributes are modified by static, they are guaranteed to be instantiated only once, and the instantiation order is strictly guaranteed.
//Static internal class mode public class Singleton { //Private construction method 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 INSTANCE will not be initialized when the Singleton class is loaded for the first time. Only getInstance is called for the first time, and the virtual machine loads SingletonHolder
And initialize INSTANCE, which can not only ensure thread safety, but also ensure the uniqueness of Singleton class.
Summary:
Static inner class singleton mode is an excellent singleton mode, which is commonly used in open source projects. Without any lock, it ensures the safety of multithreading without any performance impact and waste of space.
-
Enumeration mode
Enumeration class implementation singleton mode is the highly recommended singleton implementation mode, because the enumeration type is thread safe and can only be loaded once. The designer makes full use of this feature of enumeration to realize the singleton mode. The writing method of enumeration is very simple, and the enumeration type is the only singleton implementation mode that will not be destroyed.
//Enumeration mode public enum Singleton { INSTANCE; }
explain:
The enumeration method belongs to the hungry Chinese method.
4.1.3 existing problems
4.1.3.1 problem demonstration
Destroy singleton mode:
Enables the Singleton class defined above to create multiple objects, except enumeration. There are two ways, serialization and reflection.
-
Serialization and deserialization
Singleton class:
public class Singleton implements Serializable { //Private construction method 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:
public class Test { public static void main(String[] args) throws Exception { //Write objects to files //writeObject2File(); //Read object from file Singleton s1 = readObjectFromFile(); Singleton s2 = readObjectFromFile(); //Judge whether two deserialized objects are the same object System.out.println(s1 == s2); } private static Singleton readObjectFromFile() throws Exception { //Create object input stream object ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt")); //First read Singleton object Singleton instance = (Singleton) ois.readObject(); return instance; } public 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\\Think\\Desktop\\a.txt")); //Write the instance object to the file oos.writeObject(instance); } }
The running result of the above code is false, indicating that serialization and deserialization have broken the singleton design pattern.
-
reflex
Singleton class:
public class Singleton { //Private construction method 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 bytecode object of the Singleton class Class clazz = Singleton.class; //Gets the private parameterless constructor 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(); //Judge whether two Singleton objects created by reflection are the same object System.out.println(s1 == s2); } }
The running result of the above code is false, indicating that serialization and deserialization have broken the singleton design pattern
Note: the enumeration method will not cause these two problems.
4.1.3.2 problem solving
-
Solution of destroying singleton pattern by serialization and deserialization
Add the readResolve() method to the Singleton class. It is called by reflection during deserialization. If this method is defined, it returns the value of this method. If it is not defined, it returns the new object.
Singleton class:
public class Singleton implements Serializable { //Private construction method 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 solve the serialization and deserialization cracking singleton mode //When deserializing, the method will be called automatically and the return value of the method will be returned directly private Object readResolve() { return SingletonHolder.INSTANCE; } }
Source code analysis:
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 { ... //Isinstantable returns true and executes desc.newInstance() to create a new singleton class through reflection, obj = desc.isInstantiable() ? desc.newInstance() : null; ... // After adding readResolve method in Singleton class, the execution result of desc.hasReadResolveMethod() method is true if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { // Call the readResolve method in the Singleton class through reflection and assign the return value to the rep variable // In this way, the readObject method in the ObjectInputStream class will be called many times, and then the readResolve method defined by us will be called, so the same object will be returned. Object rep = desc.invokeReadResolve(obj); ... } return obj; }
-
Solution of cracking single case in reflection mode
public class Singleton { //Private construction method private Singleton() { //Code to be added for reflection cracking singleton mode 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; } } }
explain:
This way is easier to understand. When the constructor is called for creation through reflection, an exception is thrown directly. Do not run this operation.
4.1.4 JDK source code analysis - Runtime class
The Runtime class is the singleton design pattern used.
-
See which singleton mode is used through the source code
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 above source code, we can see that the Runtime class uses starving Han style (static attribute) to implement the singleton mode.
-
Using methods in the Runtime class
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 that the Java virtual machine is trying to use. System.out.println(runtime.maxMemory()); //Create a new process, execute the specified string command, and return the process object Process process = runtime.exec("ipconfig"); //Get the result after the command is executed, and get it 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")); } }