单例模式主要是通过把一个类的构造方法私有化,来避免重复创建多个对象的。那么,想要破坏单例,只要想办法能够执行到这个私有的构造方法就行了。
一般来说做法有使用反射及使用反序列化都可以破坏单例。
我们先通过双重校验锁的方式创建一个单例,后文会通过反射及反序列化的方式尝试破坏这个单例。
package com.hollis;
import java.io.Serializable;
/**
* Created by hollis on 16/2/5.
* 使用双重校验锁方式实现单例
*/
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;
}
}
我们尝试通过反射技术,来破坏单例:
Singleton singleton1 = Singleton.getSingleton();
//通过反射获取到构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
//将构造函数设置为可访问类型
constructor.setAccessible(true);
//调用构造函数的newInstance创建一个对象
Singleton singleton2 = constructor.newInstance();
//判断反射创建的对象和之前的对象是不是同一个对象
System.out.println(s1 == s2);
以上代码,输出结果为false,也就是说通过反射技术,我们给单例对象创建出来了一个"兄弟"。
setAccessible(true),使得反射对象在使用时应该取消 Java 语言访问检查,使得私有的构造函数能够被访问。
我们尝试通过序列化+反序列化来破坏一下单例:
package com.hollis;
import java.io.*;
/**
* Created by hollis on 16/2/5.
*/
public class SerializableDemo1 {
//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
//Exception直接抛出
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
}
}
//false
输出结构为false,说明:
通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。
这里,在介绍如何解决这个问题之前,我们先来深入分析一下,为什么会这样?在反序列化的过程中到底发生了什么。
对象的序列化过程通过ObjectOutputStream和ObjectInputStream来实现的,那么带着刚刚的问题,分析一下ObjectInputStream 的readObject
方法执行情况到底是怎样的。
为了节省篇幅,这里给出ObjectInputStream的readObject
的调用栈:
这里看一下重点代码,readOrdinaryObject
方法的代码片段: code 3
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//此处省略部分代码
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);
}
//此处省略部分代码
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;
}
code 3 中主要贴出两部分代码。先分析第一部分:
code 3.1
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);
}
这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject
返回的对象。
isInstantiable
:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。
desc.newInstance
:该方法通过反射的方式新建一个对象。
然后看一下newInstance
的源码:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
其中关键的就是T inst = (T) ca.newInstance(initargs);
这一步,这里实现的话在BootstrapConstructorAccessorImpl中,实现如下:
public Object newInstance(Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
try {
return UnsafeFieldAccessorImpl.unsafe.
allocateInstance(constructor.getDeclaringClass());
} catch (InstantiationException e) {
throw new InvocationTargetException(e);
}
}
可以看到,这里通过Java 的 Unsafe 机制来创建对象的,而不是通过调用构造函数。这意味着即使类的构造函数是私有的,反序列化仍然可以创建该类的实例,因为它不依赖于常规的构造过程。
所以。到目前为止,也就可以解释,为什么序列化可以破坏单例了?
答:序列化会通过Unsafe直接分配内存的方式来创建一个新的对象。
在涉及到序列化的场景时,要格外注意他对单例的破坏。
反射是调用默认的构造函数创建出来的,只需要我们改造下构造函数,使其在反射调用的时候识别出来对象是不是被创建过就行了:
private Singleton() {
if (singleton != null){
throw new RuntimeException("单例对象只能创建一次... ");
}
解决反序列化的破坏单例,只需要我们自定义反序列化的策略就行了,就是说我们不要让他走默认逻辑一直调用到Unsafe创建对象,而是我们干预他的这个过程,干预方式就是在Singleton类中定义readResolve
,这样就可以解决该问题:
package com.hollis;
import java.io.Serializable;
/**
* Created by hollis on 16/2/5.
* 使用双重校验锁方式实现单例
*/
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;
}
}
还是运行以下测试类:
package com.hollis;
import java.io.*;
/**
* Created by hollis on 16/2/5.
*/
public class SerializableDemo1 {
//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
//Exception直接抛出
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
}
}
//true
本次输出结果为true。具体原理,我们回过头继续分析code 3中的第二段代码:
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);
}
}
hasReadResolveMethod
:如果实现了serializable 或者 externalizable接口的类中包含readResolve
则返回true
invokeReadResolve
:通过反射的方式调用要被反序列化的类的readResolve方法。
所以,原理也就清楚了,只要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。