反射破坏单例模式
破坏方法
在Java中,反射技术可以获取类的所有方法、成员变量,还能访问私有的变量和方法。所以private的构造器也是可以被获取到并执行的。这样一来,单例模式中的的私有构造器被反射多次调用时,就会创建多个实例,进而破坏单例模式。下面是sample:
|
|
|
|
伪解决办法
反射也是要通过私有构造器来实例化,那可以在构造器里面加一个判断,如果已经实例化了,就抛出一个异常。看代码:
main的执行结果:
看起来效果达到了,常规方法实例化单例后,再用反射技术实例化,JVM抛出异常了,单例模式得到了保证。
但是,这样就真的万无一失了吗?
不!因为我们仍然可以用反射技术去修改静态变量isInit的值。来看客户端的代码:
序列化
破坏方法
除了反射外,序列化和反序列化也会破坏单例模式。我们直接看代码:
|
|
测试代码输出结果:
序列化的实例和反序列化出来的实例不是同一个,说明单例模式被破坏了。
解决办法
为了引出序列化破坏单例的解决方法,我们得先来看下实例是怎么被序列化和反序列化的。
对象的序列化过程是通过ObjectOutputStream和ObjectInputStream来实现的,那么我们来分析一下ObjectInputStream的readObject
方法到底是怎么执行的。
通过跟踪堆栈,我们可以获取到反序列化时ObjectInputStream的readObject的调用栈:
readObject -> readObject0 -> readOrdinaryObject -> checkResolve
我们来重点看一下readOrdinaryObject方法的部分代码:
这里又分两个代码片段,我们先看CodeBlock1
这里创建的obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject方法返回的对象。
desc.isInstantiable():如果一个类实现了Serializable/Externalizable并且可以在运行时被序列化,那么该方法就返回true;
desc.newInstance():该方法通过反射技术调用无参构造器实例化一个对象;
所以,到这里,也就能明白为什么序列化可以破坏单例,对根到底,还是使用反射来实现的。
为了解决反序列化破坏单例,我们需要继续看CodeBlock2:
desc.hasReadResolveMethod():如果实现了Serializable或Externalizable的类中包含
readResolve
方法,则返回true;
desc.invokeReadResolve(obj):通过反射的方式调用被反序列化的类中的readResolve
方法。
所以,为了解决序列化破坏的单例,我们只要在单例类中定义一个readResolve
方法,并在该方法中指定要返回对象的生成策略,就可以防止单例被破坏了。下面是代码:
多个类加载器
类加载器,是用来把类(Class)装载进JVM的,JVM定义了两种类型的类加载器,一种是JVM缺省的加载器,包括Bootstrap加载器、Extension加载器和System加载器,另外一种是用户自定义的加载器,只要继承自java.lang.ClassLoader类就好了。所以,在一个JVM实例中,可能存在多个ClassLoader,每个ClassLoader拥有自己的命名空间namespace。一个ClassLoader只能拥有一个class对象类型的实例,但是不同的ClassLoader可能拥有多个class对象的实例,这会对单例模式产生致命的问题。比如ClassLoaderA装载类A的实例A1,而ClassLoaderB也装载了类A的实例A2,本来A1==A2应该是true的,但是由于A1和A2来自不同的ClassLoader,它们实际上是完全不同的,如果A中定义了一个静态变量c,那么c在不同的ClassLoader的值是不同的。
为了解决这个问题,我们要做的是:
- 要么保证“多个ClassLoader不会装载同一个单例类”;
- 要么让多个ClassLoader继承自同一个父ClassLoader,这样多个ClassLoader都会通过同一个父ClassLoader去装载单例类,进而避免装载多个单例类;