Singleton mode, which should be understood by all java developers, is not necessary for those defective singleton modes. Too low. Here are two kinds of singleton modes.
1, Double check lock
First code:
package com.ayo.singleton; import java.io.*; /** * Double check lock single instance mode * * @Authror ayo * @Date 2020/1/7 14:33 */ public class LanhanSingleton implements Serializable { private volatile static LanhanSingleton INSTANCE; private LanhanSingleton() {} public static LanhanSingleton getInstance(){ if(INSTANCE == null){ synchronized (LanhanSingleton.class){ if (INSTANCE == null){ INSTANCE = new LanhanSingleton(); } } } return INSTANCE; } }
The double check lock is basically a relatively perfect single instance mode (in most scenarios), but it still has disadvantages. If you write it like this, you can't really guarantee a single instance. The simplest way is to use reflection to trick you:
package com.ayo.singleton; import java.io.*; import java.lang.reflect.Constructor; /** * Double check lock single instance mode * * @Authror ayo * @Date 2020/1/7 14:33 */ public class LanhanSingleton implements Serializable { private volatile static LanhanSingleton INSTANCE; private LanhanSingleton() {} public static LanhanSingleton getInstance(){ if(INSTANCE == null){ synchronized (LanhanSingleton.class){ if (INSTANCE == null){ INSTANCE = new LanhanSingleton(); } } } return INSTANCE; } public static void main(String[] args) throws Exception { /** * Reflection get object */ LanhanSingleton instance = LanhanSingleton.getInstance(); //Get constructor of class Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor(); lanhanSingletonConstructor.setAccessible(true); LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance(); System.out.println("Is the object obtained by reflection the same as the singleton object? = " + (instance == lanhanSingleton1)); } }
This is a good prevention, because the class is defined by yourself
package com.ayo.singleton; import java.io.*; import java.lang.reflect.Constructor; /** * Double check lock single instance mode * * @Authror ayo * @Date 2020/1/7 14:33 */ public class LanhanSingleton implements Serializable { private volatile static LanhanSingleton INSTANCE; private LanhanSingleton() { //Stopping reflex if (INSTANCE != null && this != INSTANCE){ throw new SecurityException("Reflect on your sister! "); } } public static LanhanSingleton getInstance(){ if(INSTANCE == null){ synchronized (LanhanSingleton.class){ if (INSTANCE == null){ INSTANCE = new LanhanSingleton(); } } } return INSTANCE; } public static void main(String[] args) throws Exception { LanhanSingleton instance = LanhanSingleton.getInstance(); /** * Reflection get object */ //Get constructor of class Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor(); lanhanSingletonConstructor.setAccessible(true); LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance(); System.out.println("Is the object obtained by reflection the same as the singleton object? = " + (instance == lanhanSingleton1)); } }
Reflection is easy to disable, but there is another method, deserialization, which can also generate a new object. Take a look
package com.ayo.singleton; import java.io.*; /** * Double check lock single instance mode * * @Authror ayo * @Date 2020/1/7 14:33 */ public class LanhanSingleton implements Serializable { private volatile static LanhanSingleton INSTANCE; private LanhanSingleton() { //Stopping reflex if (INSTANCE != null && this != INSTANCE){ throw new SecurityException("Reflect on your sister! "); } } public static LanhanSingleton getInstance(){ if(INSTANCE == null){ synchronized (LanhanSingleton.class){ if (INSTANCE == null){ INSTANCE = new LanhanSingleton(); } } } return INSTANCE; } public static void main(String[] args) throws Exception { /** * serialize */ //Get output stream ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("lanhanFile")); //Serialize singleton objects to disk oos.writeObject(LanhanSingleton.getInstance()); //Get input stream ObjectInputStream ois = new ObjectInputStream(new FileInputStream("lanhanFile")); //Read serialized singleton object from disk LanhanSingleton lanhanSingleton = (LanhanSingleton) ois.readObject(); //Judge whether the two are equal System.out.println("Is the deserialized object equal to the original? = " + (lanhanSingleton == LanhanSingleton.getInstance())); } }
I don't think it's silly. Why does the object change after deserialization? Let's go in and have a look (mark your breakpoint debug)
When following the source code, you can see the following methods:
ok, when you follow here, you find that reflection is called (using the constructor in ObjectStreamClass, so is it useless for you to add verification to the constructor? No, this kind of constructor can't be adjusted by the programmer, but you can't forbid it, because it will be adjusted during deserialization, and there will be problems in the same way. What should I do , I know you're confused again. Don't worry. Keep reading the source code
Carefully debug this logic and set breakpoints. At last, you will find that it has entered the hasReadResolveMethod method,
As a result, the expression desc.hasReadResolveMethod() is false. After further analysis, it is found that this readResolveMethod is actually a reflection method to obtain the readResolve method in desc (COM. Ayo. Singleton. Lanhansingleton: static final long serialVersionUID = - 65557666772060599999l;). If this method is not empty, the returned object is actually the return value of this method. Add a method
private Object readResolve(){ return INSTANCE; }
It's settled.
Two. Enumeration
Directly on the code (tired, see the source code really see eye ache)
package com.ayo.singleton; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; /** * Enumeration based singleton mode * * @Authror ayo * @Date 2020/1/7 14:53 */ public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } public static void main(String[] args) throws Exception { /** * serialize */ //Get output stream ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enumFile")); //Serialize singleton objects to disk oos.writeObject(INSTANCE); //Get input stream ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumFile")); //Read serialized singleton object from disk EnumSingleton enumSingleton = (EnumSingleton) ois.readObject(); //Judge whether the two are equal System.out.println("Is the deserialized object equal to the original? = " + (INSTANCE == enumSingleton)); Thread.sleep(1000); /** * Reflection get object */ //Get constructor of class Constructor<EnumSingleton> enumSingletonConstructor = EnumSingleton.class.getDeclaredConstructor(); enumSingletonConstructor.setAccessible(true); EnumSingleton enumSingletonReflect = enumSingletonConstructor.newInstance(); System.out.println("Is the object obtained by reflection the same as the singleton object? = " + (INSTANCE == enumSingletonReflect)); } }
At this time, you will find the advantages of enumerating singletons. Both problems are avoided. Why?
First, deserialization,
The previous steps are the same as ordinary objects, but in this step, you will find that the enumeration is TC? Enum. Analyze the readEnum method, and see the valueof method
And then there's reflection,
This is simpler. In fact, it is not allowed to reflect enumeration. Through the source code analysis, we can know that enumeration classes are specially treated, so the natural support for singleton mode.