实现了单例模式之后,也有可能会出现单例类创建了两个或两个以上实例的情况,这种情况被称为“单例模式被破坏”


反射破坏单例模式的情景

被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;
}