码出未来互联网科技Java架构技术进阶

单例模式中总有几个歪门邪道的问题要知道!

2021-03-12  本文已影响0人  老男孩_Misaya

推荐阅读:

前言

单例模式是一种应用很广泛的设计模式,提起关于他的问题,我们可以讨论到懒汉、饿汉、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方法,类可以直接控制自己反序列化的实例的类型。

上一篇 下一篇

猜你喜欢

热点阅读