Serialization breaks the singleton pattern
When using double checklocks and using volatile, this seemingly perfect approach can also have the problem of breaking the singleton pattern when serialization is encountered.
Q: Is the serialized object still the same
The answer is no
public class Singleton implements Serializable{ private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }}Copy the code
public class Test{ public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(Singleton.getSingleton()); File file = new File("tempFile"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Singleton newInstance = (Singleton) ois.readObject(); System.out.println(newInstance == Singleton.getSingleton()); }}Copy the code
The result is false
This is because ObjectInputStream calls the readOrdinaryObject method when the object is serialized
The readOrdinaryObject method is source code at the end
Reads and returns “ordinary” (i.e., not a String, Class, ObjectStreamClass, array, or enum constant) object, or null if object’s class is unresolvable (in which case a ClassNotFoundException will be associated with object’s handle). Sets passHandle to object’s assigned handle. An “ordinary” (that is, not a string, class, ObjectStreamClass, array, or enumerated constant) object is read and returned, or NULL if the object’s class is not resolvable (in which case ClassNotFoundException will be associated with the object’s handle). Set passHandle to the specified handle of the object.
ReadOrdinaryObject Core Code
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
Copy the code
IsInstantiable, source code interpretation is
Returns true if represented class is serializable/externalizable and can be instantiated by the serialization runtime–i.e., if it is externalizable and defines a public no-arg constructor, or if it is non-externalizable and its first non-serializable superclass defines an accessible no-arg constructor. Otherwise, returns false. If said is serializable/externalizable classes, and can be instantiated by the serialization runtime, that is, if the said classes can be externalized, and defines the no-arg constructor of the public, or said class is an externalized, and its first serialized superclass defines the accessible parameterless constructor, it returns true. Otherwise, return false.
IsInstantiable will determine if the class we want to serialize can be serialized and return true if so. If true, reflection calls newInstance to create a new object. It does not return null, so serialization breaks the singleton pattern!
Ii. How to prevent singleton pattern from being broken
As long as a thing can see the source code, then we can certainly dig out the information we want according to the source code.
ReadOrdinaryObject (2)
if (obj ! = null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep ! = obj) { handles.setObject(passHandle, obj = rep); }}Copy the code
hasReadResolveMethod
Returns true if represented class is serializable or externalizable and defines a conformant readResolve method. Otherwise, returns false. Returns true if the represented class is serializable or externalizable and defines a consistent readResolve method. Otherwise, return false.
HasReadResolveMethod: Returns true if the readResolve method is defined in the serialized class
invokeReadResolve
Invokes the readResolve method of the represented serializable class and returns the result. Throws UnsupportedOperationException if this class descriptor is not associated with a class, or if the class is non-serializable or does not define readResolve. Call the readResolve method of the represented serializable class and return the result. If such a descriptor is not associated with a class, or the class cannot be serialized or undefined readResolve, cause UnsupportdOperationException.
InvokeReadResolve: call by means of reflection to be deserialized readResolve method of a class, if there is no readResolve method, will throw an UnsupportedOperationException.
Therefore, defining the readResolve method in the singleton class and returning the singleton object in the readResolve method prevents the singleton pattern from being broken
public class Singleton implements Serializable{ private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } private Object readResolve() { return singleton; }}Copy the code
It is said that reflection breaks the singleton pattern, but serialization also breaks the singleton pattern, but you may also think that the most fundamental reason why serialization breaks the singleton pattern is also because reflection creates objects to break!
ReadOrdinaryObject method source code
private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() ! = TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<? > cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx ! = null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); if (obj ! = null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep ! = obj) { // Filter the replacement object if (rep ! = null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; }Copy the code