单例模式的实现方式有很多种,详情可以参考单例模式的7种实现方式及分析,从线程安全以及懒加载等角度来看其中第6种(double check)和第7种(静态内部类)的实现方式都是值得推荐并且应用广泛的,但是它们(包括第1到第7种)都有一个痛点,就是无法阻止通过反射或者序列化来破解单例对象的唯一性
反射破解
下列代码以double check方式实现的单例模式为示例,详情如下:
- 代码
public class SyncDoubleCheckLazy {
private SyncDoubleCheckLazy() {
}
private static SyncDoubleCheckLazy singleton = null;
public static SyncDoubleCheckLazy getSingleton() {
if (singleton == null) {
synchronized (SyncDoubleCheckLazy.class) {
if (singleton == null) {
singleton = new SyncDoubleCheckLazy();
}
}
}
return singleton;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通过getSingleton方法获取的单例对象
SyncDoubleCheckLazy originalSingleton = SyncDoubleCheckLazy.getSingleton();
//通过反射创建的非单例对象
//这里插播一条小知识,getConstructor无法获取到私有的构造方法,因为getConstructor只返回声明为public的构造方法,
//但是getDeclaredConstructor可以,这个方法会返回所有构造器,包括public的和非public的;
//这个规则同样适用于其它反射操作;
SyncDoubleCheckLazy newSingleton = SyncDoubleCheckLazy.class.getDeclaredConstructor().newInstance();
//打印结果看看这两个对象是否是一个
System.out.println(originalSingleton == newSingleton);
}
}
- 打印结果
false
序列化破解
下列代码以double check方式实现的单例模式为示例,详情如下:
- 代码
public class SyncDoubleCheckLazy implements Serializable {
private SyncDoubleCheckLazy() {
}
private static SyncDoubleCheckLazy singleton = null;
public static SyncDoubleCheckLazy getSingleton() {
if (singleton == null) {
synchronized (SyncDoubleCheckLazy.class) {
if (singleton == null) {
singleton = new SyncDoubleCheckLazy();
}
}
}
return singleton;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
//通过getSingleton方法获取的单例对象
SyncDoubleCheckLazy originalSingleton = SyncDoubleCheckLazy.getSingleton();
//通过序列化创建的非单例对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("newSingleton"));
oos.writeObject(originalSingleton);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("newSingleton"));
SyncDoubleCheckLazy newSingleton = (SyncDoubleCheckLazy) ois.readObject();
ois.close();
//打印结果看看这两个对象是否是一个
System.out.println(originalSingleton == newSingleton);
}
}
- 打印结果
false
经过前面的铺垫之后,我们接下来就进入我们的正题了,即枚举单例模式以及它是如何避免反射和序列化来破解单例模式的;
枚举单例代码实现
public enum EnumSingleton {
INSTANCE;
}
反射破解
- 代码
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//直接获取原单例对象
EnumSingleton originalSingleton = EnumSingleton.INSTANCE;
//通过反射创建的非单例对象
EnumSingleton newSingleton = EnumSingleton.class.getDeclaredConstructor().newInstance();
//打印结果看看这两个对象是否是一个
System.out.println(originalSingleton == newSingleton);
}
- 打印结果
Exception in thread "main" java.lang.NoSuchMethodException: org.jc.framework.javasupport.EnumSingleton.<init>(int)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.jc.framework.javasupport.EnumSingleton.main(EnumSingleton.java:16)
通过以上测试我们知道枚举类型在java中是无法通过反射来创建的,否则会抛异常;
序列化破解
- 代码
public static void main(String[] args) throws IOException, ClassNotFoundException {
//直接获取原单例对象
EnumSingleton originalSingleton = EnumSingleton.INSTANCE;
//通过序列化创建的非单例对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("newSingleton"));
oos.writeObject(originalSingleton);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("newSingleton"));
EnumSingleton newSingleton = (EnumSingleton) ois.readObject();
ois.close();
//打印结果看看这两个对象是否是一个
System.out.println(originalSingleton == newSingleton);
}
- 打印结果
true
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法;
其次枚举单例的线程安全性是通过jvm的类加载机制来保证的,无需我们操心,但是对我来说,枚举单例的懒加载是值得商榷的一点,如果枚举中定义了静态变量/常量/方法的话,那么当使用的时候可能会破坏单例的懒加载!