单例模式中总有几个歪门邪道的问题要知道!
推荐阅读:
前言
单例模式是一种应用很广泛的设计模式,提起关于他的问题,我们可以讨论到懒汉、饿汉、synchronized锁,volatile、以及如何做双重判断,以至于最后我们写出来的单例是这样的:
public class TestSingleton {
private volatile static TestSingleton testSingleton =null;
public static TestSingleton getInstance(){
if (testSingleton==null){
synchronized (TestSingleton.class){
if (testSingleton==null){
testSingleton =new TestSingleton();
}
}
}
return testSingleton;
}
private TestSingleton(){}
}
又或者在为了优化性能,而使用静态内部类的方式,但是我们今天不讨论于此,而是讨论单例的破坏,破坏就是想尽办法创建出让原本应该单例的对象的多个不同实例,这真是一种无聊的的情况...
反射破坏
反射作为Java中的一个强大功能,可以直接暴力的new对象,即使对象的构造方法是private的,但建无妨,所以如果构造方法没有做特殊处理的话,这就会导致生成新的实例,就像下面这样,输出结果肯定是false。
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<TestSingleton> declaredConstructor = TestSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
TestSingleton testSingleton = declaredConstructor.newInstance();
System.out.println(testSingleton ==TestSingleton.getInstance());//false
}
对此,我们肯定需要解决,要防止一些别有用心的人。
解决方式可以利用静态类的特性来处理,当使用TestSingleton的时候,默认会先加载内部类,我们只要在构造方法中判断实例是不是不为空,不为空说明已经加载了,则抛出异常。
public class TestSingleton implements Serializable {
public static TestSingleton getInstance() {
return TestSingletonHolder.test;
}
private TestSingleton(){
if (TestSingletonHolder.test!=null){
throw new RuntimeException("不允许重复创建");
}
}
private static class TestSingletonHolder{
private static final TestSingleton test= new TestSingleton();
}
}
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at Main.main(Main.java:22)
Caused by: java.lang.RuntimeException: 不允许重复创建
at TestSingleton.<init>(TestSingleton.java:11)
... 5 more
序列化破坏
有没有过这样的场景,一个单例模式创建好后,需要将对象序列化后写入到磁盘,下次再从磁盘中反序列化,转换成对象,但是问题就在于,反序列化后的对象会重新分配内存,重新创建,这就违背了单例的初衷。
public class Main extends JFrame {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("TestSingleton.obj"));
objectOutputStream.writeObject(TestSingleton.getInstance());
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("TestSingleton.obj"));
TestSingleton testSingleton = (TestSingleton) objectInputStream.readObject();
System.out.println(testSingleton);
System.out.println(TestSingleton.getInstance());
}
}
输出如下,可以看到反序列化后的对象和getInstance()是两个不同的对象。
TestSingleton@b4c966a
TestSingleton@7ef20235
那么这个问题该如何解决?即保证反序列化后的对象和getInstance()是同一个呢?
其实很简单,只要在单例类中加个readResolve()方法即可。
public Object readResolve(){
return TestSingletonHolder.test;
}
重新运行后就会得到两个相同的对象。
TestSingleton@7ef20235
TestSingleton@7ef20235
所以,readResolve()是何方圣神,居然有如此神奇之处?。
readResolve()解析
先来盲猜一波,会不会是readObject()读取的时候,发现对象中有一个"协议"方法,这个"协议"方法就是readResolve(),他会阻止readObject()继续返回默认创建的对象,转手调用并返回此对象readResolve()的值,而readResolve()的值就是我们真正的单例对象的值。
按照JDK的源码风格,那么在他的源码中,可能会有一个方法,用来判断readResolve()这个方法存不存在,命名可能是hasReadResolve、ExistReadResolve等方式,那么我们直接在ObjectInputStream的源码中搜索一下,看看有没有。
果不其然,搜索到了下面的代码,其中有段调用了hasReadResolveMethod(),看名字就知道,这是用来判断有没有ReadResolve方法的。
至于readOrdinaryObject方法,是ObjectInputStream.readObject()一层层调用到这里的。当被反序列化的类中存在readResolve()方法时,就会走进这个if代码块,他会通过反射调用readObject()方法,并返回此方法返回的对象。但是在前一步还是先会创建新的对象,接着才判断有没有readResolve()方法,如果有,把变量obj替换成readResolve()返回的对象,如果没有,就会返回新创建的。
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
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);
}
}
}
另外在看下hasReadResolveMethod()方法,他直接返回了readResolveMethod是不是为空,那么就意味着,在某处会从这个类中通过反射获取这个类的readResolve()方法。
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
所以,初始化readResolveMethod的地方在ObjectStreamClass的构造方法中,构造方法的cl参数就是被反序列化的Class对象。
private ObjectStreamClass(final Class<?> cl) {
....
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
}
所以总结一句话概括readResolve方法就是:允许一个类替换从流中读取的对象,然后将其返回给调用方。通过实现readResolve方法,类可以直接控制自己反序列化的实例的类型。