单例模式的破坏
单例模式只会实例化一个对象,但是通过一些方式是可以破坏掉这种模式的
破坏单例模式的两种方式以及阻止破坏
序列化破坏
使用之前的懒汉式的静态内部类方式示例,在该对象上实现 java 自带的序列化接口
public class Singleton implements Serializable {
private Singleton(){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
执行下面的代码可以看到结果是 false
public static void main(String[] args) throws Exception {
//序列化
Singleton instance1 = Singleton.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("文件名"));
out.writeObject(instance1);
//反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("文件名"));
Singleton instance2 = (Singleton) in.readObject();
System.out.println(instance1 == instance2);
}
在内存中他们是两个地址就证明这是两个对象,这样就是破坏了单例模式.为什么会出现这样的结果呢?
原因:在 java 自带的序列化中,在反序列化的时候其实是调用了反射了帮我们创建了一个新的对象,所以破坏了单例模式
序列化阻止
public class Singleton implements Serializable {
private Singleton(){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
@Serial // 这个注解 jdk 14 之后增加的 *作用是与@Override*类似,此注释与串行 lint 标志结合使用,以对类的与序列化相关的成员执行编译时检查
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
再次执行上面的 main 方法得到的结果是true
原因:在序列化的时候 desc.hasReadResolveMethod() 会判断是否有 readResolve() 方法,如果存在的 会执行 desc.invokeReadResolve(obj) 方法,这样创建的对象就是同一个对象
反射破坏
public static void main(String[] args) throws Exception {
// 正常创建的对象
Singleton instance = Singleton.getInstance();
// 获取到私有的构造器
Constructor<? extends Singleton> constructor = instance.getClass().getDeclaredConstructor();
// 因为构造器是私有的,正常情况下是不可被修改的,但是可以使用强制手段
constructor.setAccessible(true);
// 通过反射创建的对象
Singleton singleton = constructor.newInstance();
System.out.println(instance==singleton);
}
执行以上代码可以得到的结果是 fasle
反射阻止
public class Singleton {
private static boolean flag = false;
private Singleton(){
synchronized (Singleton.class) {
if (false) {
throw new RuntimeException("对象已经被创建了");
}
flag = true;
}
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
执行以上代码时会抛出异常,强制停止
这个代码其实不算是解决方法,因为还可以通过反射去修改 flag 的值再次去破坏对象,看起来好像是无解的
最佳的单例模式方案
使用 enum 来创建
- 1: JVM会保证enum不能被反射并且构造器方法只执行一次
- 2: 此方法无偿提供了序列化机制,绝对防止反序列化时多次实例化
- 3: 运行时(compile-time )创建对象(懒加载)
- 4: enum 实在 jdk 5 加入的,所以 jdk 版本要 >=5