实现了单例模式之后,也有可能会出现单例类创建了两个或两个以上实例的情况,这种情况被称为“单例模式被破坏”
反射破坏单例模式的情景
被private关键字修饰的方法,被其他类调用会导致编译不通过,这是出于代码规范和开发效率的考虑。换句话说就是其他类在运行时可以通过反射调用被private关键字修饰的方法。如果反射调用的是单例类的构造方法,是否可以破坏单例模式呢?我们写一段代码来测试一下:
try {
Class<?> clazz = Singleton.class;
// 通过反射获取私有的构造方法
Constructor c = clazz.getDeclaredConstructor(null);
// 强制访问
c.setAccessible(true);
// 暴力初始化
Object o1 = c.newInstance();
Object o2 = c.newInstance();
System.out.println("比较原型对象与反射对象:"+(o1 == o2));
} catch (Exception e) {
e.printStackTrace();
}
测试结果如下:
比较原型对象与反射对象:false |
由此可见,反射也是可以破坏单例模式的。
反序列化破坏单例模式的情景
一个单例对象创建好之后,有时候需要将对象序列化然后进行数据持久化,下次使用的时候通过反序列化转化为内存对象。反序列化后的对象会重新分配内存,会破坏单例模式。
首先看支持序列化的单例类的代码:
// 实现Serializable即可实现序列化和反序列化
public class Singleton implements Serializable {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
测试代码如下所示:
Singleton singleton = Singleton.getInstance();
Singleton singletonSerializable = null;
FileOutputStream fos = null;
try {
fos = new FileOutputStream("Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
singletonSerializable = (Singleton) ois.readObject();
ois.close();
System.out.println("比较原型对象与反序列化对象:"+(singleton == singletonSerializable));
} catch (Exception e) {
e.printStackTrace();
}
运行结果:
比较原型对象与反序列化对象:false |
可见单例模式会被反序列化破坏。
除了Java自带的反序列化方式以外,实际开发过程中更常见的是用FastJson或Moshi等开源框架将对象序列化成JSON或Protobuf,以下是测试Gson反序列化破坏单例的代码:
Gson gson = new Gson();
String json = gson.toJson(singleton);
singletonSerializable = gson.fromJson(json,Singleton.class);
System.out.println("比较原型对象与反序列化对象:"+(singleton == singletonSerializable));
运行结果:
比较原型对象与反序列化对象:false |
可见单例模式也会被JSON等其他格式的反序列化破坏,Jackson等其他框架同理。
clone()方法破坏单例模式的情景
Java的对象不仅可以new关键字产生,也可以利用clone()方法产生,或者利用反射和反序列化产生。用DCL或静态内部类实现单例模式都是利用private修饰构造函数来解决new关键字产生多个对象的问题的,但单例模式仍然可以被clone()方法、反射和反序列化破坏。
如下代码所示,让单例类实现Cloneable接口,同时重写clone()方法:
// Cloneable接口的作用是让clone()生效
public class Singleton implements Cloneable{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
@Override
public Singleton clone(){
try{
return (Singleton) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
比较原始对象与克隆对象
Singleton singleton = Singleton.getInstance();
Singleton singletonClone = singleton.clone();
System.out.println("比较原型对象与克隆对象:"+(singleton == singletonClone));
运行结果:
比较原型对象与克隆对象:false |
从结果上看,确实创建了两个不同的对象。为了防止clone()方法破坏单例模式的解决思路非常简单,只要单例类不实现Cloneable接口即可,或者用如下方式重写clone()方法:
@Override
public Singleton clone(){
return instance;
}