设计模式之单例模式(下)(创建者模式)

74 阅读2分钟

单例模式的破坏

单例模式只会实例化一个对象,但是通过一些方式是可以破坏掉这种模式的

破坏单例模式的两种方式以及阻止破坏

序列化破坏

使用之前的懒汉式的静态内部类方式示例,在该对象上实现 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) 方法,这样创建的对象就是同一个对象

Screenshot-2.png Screenshot-4.png

Screenshot-1.png

反射破坏

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