什么是单例模式?
单例保证一个对象JVM中只能有一个实例
这里提供一个双重锁校验单例,并且引出了序列化破坏的问题,本篇来说一下这个原理和解决
首先把上一篇的单例代码拿过来贴上:
java
/**
* 双重锁校验的单例
*/
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;
}
}
使用上面这个代码进行序列化操作的时候,会出现不同的实例,举个例子:
java
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
DoubleLock doubleLock = DoubleLock.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("doubleLock_obj"));
oos.writeObject(doubleLock);
File file = new File("doubleLock_obj");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
DoubleLock newDoubleLock = (DoubleLock) ois.readObject();
System.out.println(doubleLock);
System.out.println(newDoubleLock);
System.out.println(doubleLock == newDoubleLock);
}
}
输出结果:(第一次运行会报错,这里略过不介绍了,再运行一次就好了)
com.ygz.designpatterns.singleton.DoubleLock@27c170f0
com.ygz.designpatterns.singleton.DoubleLock@5451c3a8
false
可以看到,经过一次输出输入流,产生了两个DoubleLock对象,这是为什么呢?我们点进去上面测试代码的ois.readObject()
方法,进入源码看:
java.io.ObjectInputStream中:
java
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
再点进去readObject0(false)
方法:
这个方法其实就是读取对象的方法
java
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
因为我们是对Objcet进行操作,所以看
java
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
点进去看readOrdinaryObject(unshared)
方法:
java
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) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
可以看到这样一段代码:obj = desc.isInstantiable() ? desc.newInstance() : null;
这段代码的意思是:desc.isInstantiable()
如果为true就创建一个新的对象,否则返回null 那么desc.isInstantiable()
是什么意思呢?点进去看。。。。
java
/**
* 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()
给大家在源码里面标记了"☆☆★★",点击去看这个方法:
java
/**
* 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
那么解决方法就出来了,看这个新的序列化安全的单例模式:
java
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类;
java
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",就会看到这段代码:
java
/**
* 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;
}
可以在这段代码中看到:
java
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
这个代码中就规定了"readResolve "方法的名字,然后上面那个反射方法就是readResolveMethod.invoke(obj, (Object[]) null);
,这里面的readResolveMethod 就是这个赋值的。所以方法名字就是"readResolve"。
那么在反射中调用了我们在单例中定义的"readResolve"方法,这个方法返回了我们已经创建的单例实例,所以读取的类就成了我们在单例中创建的类,而不是上面三目运算创建的新的实例。
好了!到此为止,序列化破坏单例和其解决方式,都通过源码分析了,大家可以自己跟着源码看看,动手实践,over。
个人浅薄理解,欢迎补充