继上篇懒汉式单例模式,线程安全与性能都有所保障了,但是还会有其它方式破坏单例场景吗?答案是肯定的,这里我们介绍一下,反射与序列化两种破坏机制。
反射破坏单例
按静态内部类单例为例,虽然单独的构造方法用了private,但是我们可以通过反馈强制来访问,然后再调用newInstance()方法,创建出两个不能的实例,请看测试类代码。
public class InnerClassTest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 反射破坏单例
Class<?> clazz = LazyInnerClassSingleton.class;
// 通过反射获取私有构造方法
Constructor con = clazz.getDeclaredConstructor(null);
// 强制访问
con.setAccessible(true);
// 调用两次,相当于创建了两个实例,破坏了单例
Object obj1 = con.newInstance();
Object obj2 = con.newInstance();
// 判断
System.out.println("obj1:" + obj1);
System.out.println("obj2:" + obj2);
System.out.println(obj1 == obj2);
}
}
核对运行结果,你会发现,反射破坏了单例,并且创建出了两个不同的实例
那么如何去避免这种现象的发生呢,我们只需要在原来的LazyInnerClassSingleton类中,构造方法里作个判断就可以,至此最强的单例模式实现了。
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton() {
//第一次调用,会加载内部类完成实例
//第二次调用,实例已经存在,则抛出异常
if(MyInnerClass.sing != null) {
throw new RuntimeException ("不允许创建多个实例");
}
};
// 懒汉式单例,静态内部类
// 如果方法没有使用,内部类不会加载
public static final LazyInnerClassSingleton getInstance() {
// 返回结果时,先加载内部类
return MyInnerClass.sing;
}
// 默认不加载 ,只创建一次
private static class MyInnerClass {
private static final LazyInnerClassSingleton sing = new LazyInnerClassSingleton();
}
}
调整后运行结果
序列化破坏单例
一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,来看一段代码︰
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式
//从而转换一个I/O流,写入其他地方(可以是磁盘、网络I/0)
//内存中的状态会永久保存下来
//反序列化就是将己经持久化的字节码内容转换为I/0流
//通过I/0流的读取,进而将读取的内容转换为Java对象
//在转换过程中会苴新创注对象new
private SeriableSingleton() {
}
private static final SeriableSingleton ser = new SeriableSingleton();
public static SeriableSingleton getInstance() {
return ser;
}
}
测试类:
public class SerialbleTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
try {// 序列化
FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton)ois.readObject();
ois.close();
// 得到两个对象不一致
System.out.println("s1:" + s1);
System.out.println("s2:" + s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
s1:dysf.singleton.seriable.SeriableSingleton@41629346
s2:dysf.singleton.seriable.SeriableSingleton@3d4eac69
那么,我们如何保证在序列化的情况下也能够实现单例模式呢?其实很简单,只需要增加readResolve()方法即可。来看优化后的代码∶SeriableSingleton 这个类中加入方法,具体如何实现的,请查看JDK源码进行分析。
//保障单例不被反序列化破坏
private Object readResolve() {
return ser;
}