Java单例---序列化破坏单例模式原理解析(一)https://developer.aliyun.com/article/1393249
可以看到这样一段代码:obj = desc.isInstantiable() ? desc.newInstance() : null;
这段代码的意思是:desc.isInstantiable()
如果为true就创建一个新的对象,否则返回null 那么desc.isInstantiable()
是什么意思呢?点进去看。。。。
/** * 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. */ boolean isInstantiable() { requireInitialized(); return (cons != null); }
这段代码吧注释也贴出来了,可以看到这个方法的作用是:如果这个类是实现了serializable/externalizable,并且可以由序列化运行时实例化,则返回true,其他情况(非序列化或者可访问的无参构造)返回false。
那么很明显了,我们的DoubleLock 是实现了Serializable接口,所以他会返回true。
回到上面那个三目运算判断结果就会创建一个新的对象,并返回。所以,我们就会发现序列化之后出现了两个不同的DoubleLock 实例。
那么如何解决这个问题呢?
其实答案也在上面的源码中:可以找到一个调用方法desc.hasReadResolveMethod()
给大家在源码里面标记了“☆☆★★”,点击去看这个方法:
/** * Returns true if represented class is serializable or externalizable and * defines a conformant readResolve method. Otherwise, returns false. */ boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
可以看到这是一个反射有关的方法,作用是:如果表示的类是实现了serializable/externalizable的,并定义一个符合的readResolve方法则返回true,否则,返回false
那么解决方法就出来了,看这个新的序列化安全的单例模式:
import java.io.Serializable; /** * 双重锁校验的单例 */ public class DoubleLock implements Serializable { public static volatile DoubleLock doubleLock = null;//volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取) private DoubleLock(){ //构造器必须私有 不然直接new就可以创建 } public static DoubleLock getInstance(){ //第一次判断,假设会有好多线程,如果doubleLock没有被实例化,那么就会到下一步获取锁,只有一个能获取到, //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程 if(doubleLock == null){ synchronized (DoubleLock.class){ //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断doubleLock是null,他就实例化了doubleLock,然后他出了锁, //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new DoubleLock(),再创建一个实例,所以为了防止这种情况,需要第二次判断 if(doubleLock == null){ //下面这句代码其实分为三步: //1.开辟内存分配给这个对象 //2.初始化对象 //3.将内存地址赋给虚拟机栈内存中的doubleLock变量 //注意上面这三步,第2步和第3步的顺序是随机的,这是计算机指令重排序的问题 //假设有两个线程,其中一个线程执行下面这行代码,如果第三步先执行了,就会把没有初始化的内存赋值给doubleLock //然后恰好这时候有另一个线程执行了第一个判断if(doubleLock == null),然后就会发现doubleLock指向了一个内存地址 //这另一个线程就直接返回了这个没有初始化的内存,所以要防止第2步和第3步重排序 doubleLock = new DoubleLock(); } } } return doubleLock; } private Object readResolve(){ return doubleLock; } }
和之前的对比,其实就加了一个readResolve()
方法,现在再测试就没问题了,那么结合上面的源码和这个新的单例实现,再来看:
在上面的源码中desc.hasReadResolveMethod()
,就是标记“☆☆★★”的那个if,如果我们加上这个readResolve()
方法,判断结果就是true,会进入if块
执行这个代码:Object rep = desc.invokeReadResolve(obj);
,依然是点进去看啦:
java.io.ObjectStreamClass类;
Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException { requireInitialized(); if (readResolveMethod != null) { try { return readResolveMethod.invoke(obj, (Object[]) null); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof ObjectStreamException) { throw (ObjectStreamException) th; } else { throwMiscException(th); throw new InternalError(th); // never reached } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
这段代码就是利用反射区执行这个我们在单例中定义的readResolve()
方法。那么可能有人会有疑问,为什么这个方法名字是readResolve()
,而不是其他的呢?可以再ObjectStreamClass类中搜索“readResolve”,就会看到这段代码:
/** * Creates local class descriptor representing given class. */ private ObjectStreamClass(final Class<?> cl) { this.cl = cl; name = cl.getName(); isProxy = Proxy.isProxyClass(cl); isEnum = Enum.class.isAssignableFrom(cl); serializable = Serializable.class.isAssignableFrom(cl); externalizable = Externalizable.class.isAssignableFrom(cl); Class<?> superCl = cl.getSuperclass(); superDesc = (superCl != null) ? lookup(superCl, false) : null; localDesc = this; if (serializable) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { if (isEnum) { suid = Long.valueOf(0); fields = NO_FIELDS; return null; } if (cl.isArray()) { fields = NO_FIELDS; return null; } suid = getDeclaredSUID(cl); try { fields = getSerialFields(cl); computeFieldOffsets(); } catch (InvalidClassException e) { serializeEx = deserializeEx = new ExceptionInfo(e.classname, e.getMessage()); fields = NO_FIELDS; } if (externalizable) { cons = getExternalizableConstructor(cl); } else { cons = getSerializableConstructor(cl); writeObjectMethod = getPrivateMethod(cl, "writeObject", new Class<?>[] { ObjectOutputStream.class }, Void.TYPE); readObjectMethod = getPrivateMethod(cl, "readObject", new Class<?>[] { ObjectInputStream.class }, Void.TYPE); readObjectNoDataMethod = getPrivateMethod( cl, "readObjectNoData", null, Void.TYPE); hasWriteObjectData = (writeObjectMethod != null); } writeReplaceMethod = getInheritableMethod( cl, "writeReplace", null, Object.class); readResolveMethod = getInheritableMethod( cl, "readResolve", null, Object.class); return null; } }); } else { suid = Long.valueOf(0); fields = NO_FIELDS; } try { fieldRefl = getReflector(fields, this); } catch (InvalidClassException ex) { // field mismatches impossible when matching local fields vs. self throw new InternalError(ex); } if (deserializeEx == null) { if (isEnum) { deserializeEx = new ExceptionInfo(name, "enum type"); } else if (cons == null) { deserializeEx = new ExceptionInfo(name, "no valid constructor"); } } for (int i = 0; i < fields.length; i++) { if (fields[i].getField() == null) { defaultSerializeEx = new ExceptionInfo( name, "unmatched serializable field(s) declared"); } } initialized = true; }
可以在这段代码中看到:
readResolveMethod = getInheritableMethod( cl, "readResolve", null, Object.class);
这个代码中就规定了“readResolve”方法的名字,然后上面那个反射方法就是readResolveMethod.invoke(obj, (Object[]) null);
,这里面的readResolveMethod就是这个赋值的。所以方法名字就是“readResolve”。
那么在反射中调用了我们在单例中定义的“readResolve”方法,这个方法返回了我们已经创建的单例实例,所以读取的类就成了我们在单例中创建的类,而不是上面三目运算创建的新的实例。
好了!到此为止,序列化破坏单例和其解决方式,都通过源码分析了,大家可以自己跟着源码看看,动手实践,over。
个人浅薄理解,欢迎补充