Chapter 3 single example mode
1, Introduction
Singleton Pattern is a creation mode, which provides the best way to create objects. This mode involves a single class, which is responsible for creating its own objects and 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 objects of this class
be careful:
- A singleton class can only have one instance
- A singleton class must create its own unique instance
- A singleton class must provide this instance to all other objects
The purpose of singleton mode is to ensure that a class has only one instance and provide a global access to it. It solves the problem that a globally used class needs to be created and destroyed frequently, and can save system resources
2, Realize
1. Hungry Han style
Class is loaded into memory to instantiate a singleton, which is simple and practical
The constructor is private and cannot be called in other classes. The instance can only be obtained through the getInstance method
public class Singleton{ private static final Singleton INSTANCE; private Singleton(){} public static Singleton getInstance(){ return INSTANCE; } }
The disadvantage is that no matter whether an instance needs to be created or not, as long as the class is loaded (Class.forName), it will be instantiated and generate garbage objects
2. Double check lock
It is instantiated only when in use, which achieves the purpose of on-demand initialization, but it brings the problem of thread insecurity
public class Singleton{ private static Singleton INSTANCE; private Singleton(){} public Singleton getInstance(){ if(INSTANCE == null) INSTANCE = new Singleton(); return INSTACNE; } public static void main(String[] args){ for(int i = 0; i < 100; i++) new Thread(() -> System.out.println(Singleton.getInstance().hashCode()) ).start(); } } }
Suppose that both threads A and B use this singleton class, A first calls the getInstance method, judges that INSTANCE is empty, and enters the if code block, but there is no new
At this time, thread B also calls the getInstance method. Since A has not been new, thread B judges that INSTANCE is empty and enters the if code block
In this way, both A and B can create A new instance. In this way, A and B must not get the same instance, so A single instance cannot be guaranteed
We can lock the getInstance method to solve the thread safety problem
public class Singleton{ private static Singleton INSTANCE; private Singleton(){} public static synchronized Singleton getInstance(){ if(INSTANCE == null) INSTANCE = new Singleton(); return INSTANCE; } }
Although the thread safety problem is solved, it also leads to the decrease of efficiency by judging whether the method holds a lock every time it is called
We can improve efficiency by reducing the range of synchronized code blocks
public class Singleton{ private static Singleton INSTANCE; private Singleton(){} public static Singleton(){ if(INSTANCE == null){ sychronized(Singleton.class){ INSTANCE = new Singleton(); } } return INSTANCE; } }
However, it is meaningless to simply lock the instantiation. Threads A and B can enter the if code block no matter whether they hold the lock or not. Until the lock is released after the lock holder is new, the other party can still obtain the lock to create A second instance
Therefore, double inspection lock is required
Double check lock, that is, after obtaining the lock, judge whether it is empty in the synchronization code block
public class Singleton{ private static Singleton INSTANCE; private Singleton(){} public static Singleton getInstance(){ if(INSTANCE == null){ sychronized(Singleton.class){ if(INSTANCE == null) INSTANCE = new Singleton(); } } return INSTANCE; } }
However, we also ignore the problem of instruction reordering
Instance in Java = new singleton(); It will be compiled into the following JVM instructions by the compiler:
memory = allocate(); //1. Memory address of allocation object
ctorINSTANCE(memory);//2. Initialization object
INSTANCE = memory; //3. Set instance to point to the memory address just allocated
However, the order of these instructions is not invariable. They may be rearranged into the following order through JVM and CPU optimization
memory = allocate(); //1. Allocate the memory space of the object INSTANCE = memory; //3. Set INSTANCE to point to the memory address just allocated ctorInstance(memory); //2. Initialization object
When thread A finishes executing 1 and 3, the INSTANCE object has not completed initialization, but it no longer points to null. At this time, if thread B preempts CPU resources, the result of executing the judgment statement will be false, thus returning an uninitialized INSTANCE object
This requires adding a volatile modifier to ensure that the instructions are not reordered and visible. For details, see Chapter 4 Java Memory Model in [JVM]
public class Singleton{ private static volatile Singleton INSTANCEl; private Singleton(){} public static Singleton getInstance(){ if(INSTANCE == null){ sychronized(Singleton.class){ if(INSTANCE == null){ INSTANCE = new Singleton(); } } } return INSTANCE; } }
3. Static internal class
When loading an external class, the internal class will not be loaded, and the internal class will only be loaded by the JVM once, so this can also be a way to implement the singleton mode
public class Singleton{ private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
4. Enumeration
This implementation has not been widely used, but it is the best way to implement the singleton pattern and is more concise
public enum Singleton{ INSTANCE; }
3, Improvement
1. Prevent the destruction of singleton mode by reflection, cloning and serialization
Double check locks and static inner classes both implement lazy loading and thread safety, but do you think it's over? There are still three ways to destroy the singleton mode, which is why enumeration is the best way to realize the singleton mode, because it is naturally immune to these three kinds of destruction. First, enum has no constructor, so it cannot create instances through reflection; Secondly, enum prohibits overriding clone(); Furthermore, enum automatically supports the serialization mechanism to absolutely prevent multiple instantiations
Even if there are many ways to destroy, we can solve it
- Prevent reflection damage
First, define a global variable switch isfirstcrete, which is on by default
Change its state to off when it is first loaded - Prevent clone corruption
Override clone() to return the singleton object directly - Prevent serialization corruption
Add readResolve() to return the Object object
Take double check lock as an example:
public class Singleton implements Serializable,Cloneable{ private static final long serialVersionUID = 6125990676610180062L; private static Singleton INSTANCE; private static boolean isFristCreate = true; private Singleton(){ if (isFristCreate) { synchronized (Singleton.class) { if (isFristCreate) { isFristCreate = false; } } }else{ throw new RuntimeException("It has been instantiated once and cannot be instantiated again"); } } public static Singleton getInstance(){ if (INSTANCE == null) { synchronized (Singleton.class) { if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } @Override protected Singleton clone() throws CloneNotSupportedException { return INSTANCE; } private Object readResolve() { return INSTANCE; } }
Destruction test case:
public class DestroySingleton { public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //Get through getInstance() Singleton singleton = Singleton.getInstance(); System.out.println("singleton of hashCode: " + singleton.hashCode()); //Obtained by cloning Singleton clone = (Singleton) Singleton.getInstance().clone(); System.out.println("clone of hashCode: " + clone.hashCode()); //Get by serialization and deserialization ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(Singleton.getInstance()); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); Singleton serialize = (Singleton)ois.readObject(); System.out.println("serialize of hashCode: " + serialize.hashCode()); //Get by reflection Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton reflex = constructor.newInstance(); System.out.println("reflex of hashCode: " + reflex.hashCode()); } }
Operation results:
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1\lib\idea_rt.jar=65367:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;C:\Users\admin\Desktop\project\testProjct\Temp\target\classes" com.sisyphus.singleton.DestroySingleton singleton of hashCode: 460141958 clone of hashCode: 460141958 serialize of hashCode: 460141958 Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.sisyphus.singleton.DestroySingleton.main(DestroySingleton.java:36) Caused by: java.lang.RuntimeException: It has been instantiated once and cannot be instantiated again at com.sisyphus.singleton.Singleton.<init>(Singleton.java:18) ... 5 more The process has ended with exit code 1
The instance obtained by cloning and deserialization is the same instance obtained by getInstance(), but the instantiated instance cannot be obtained through reflection