在我们通常的单例方法中,通常有两种方法来击穿单例,反射和序列化
使用单元素枚举可以有效的解决这两个问题(在最后)
反射击穿单例
这是一个普通的单例的例子
public class Singleton {
private static final Singleton SINGLETON = new Singleton();
public static Singleton getSingleton(){
return SINGLETON;
}
}
用反射击穿
public class App {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = Singleton.class;
Constructor<?> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
// 单例对象
Singleton singleton = (Singleton) c.newInstance();
// 反射创建的对象
Singleton singleton1 = Singleton.getSingleton();
// 结果是false 证明并不是同一个对象
System.out.println(singleton == singleton1);
}
}
如何防止反射击穿呢?
我们在构造方法中进行一次判断
public class Singleton {
private static final Singleton SINGLETON = new Singleton();
private Singleton() {
synchronized (Singleton.class) {
if (SINGLETON != null){
throw new RuntimeException("试图破坏单例模式");
}
}
}
public static Singleton getSingleton() {
return SINGLETON;
}
}
public class App {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 单例对象
Singleton singleton1 = Singleton.getSingleton();
// 利用反射创建对象
Class<?> clazz = Singleton.class;
Constructor<?> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
// 到这里的时候会报错
Singleton singleton = (Singleton) c.newInstance();
//比较
System.out.println(singleton == singleton1);
}
}
序列化击穿单例
经过序列化再反序列化的对象已经和原对象是两个对象了(这里我就不举例子了)可以看看本文最后的链接
如果需要实例化,我们必须添加一个方法.
private Object readResolve(){
return SINGLETON;
}
引用一下文档的说明
This writeReplace method is invoked by serialization if the method
exists and it would be accessible from a method defined within the
class of the object being serialized. Thus, the method can have private,
protected and package-private access. Subclass access to this method
follows java accessibility rules.
Classes that need to designate a replacement when an instance of it
is read from the stream should implement this special method with the
exact signature.
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
This readResolve method follows the same invocation rules and
accessibility rules as writeReplace.
简单来说反序列化时可以用这个方法返回的对象替换反序列化对象
枚举单例
枚举单例十分简单,并且无法被反射和序列化(自己就能序列化)击穿
public enum EnumSingleton {
SINGLETON;
public void doSomeThing(){
// do what you what to do
}
}
EnumSingleton.SINGLETON.doSomeThing();
作者的话
单元素的枚举类型已经成为实现Singleton的最佳方法