三、反射破坏单例
一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读对象进行反序列化,将其转换为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,来看一段代码:
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
//单例模式,通过反射获取实例
public class ReflectTest { public static void main(String[] args) { try { Class<?> clazz = Singleton.class; //获取该单例类的空构造器 Constructor c = clazz.getDeclaredConstructor(null); //绕过私有权限 c.setAccessible(true); Object instance1 = c.newInstance(); Object instance2 = c.newInstance(); System.out.println(instance1); System.out.println(instance2); System.out.println(instance1 == instance2); }catch (Exception e){ e.printStackTrace(); } }
通过反射获得单例类的构造函数,由于该构造函数是private的,通过setAccessible(true)指示反射的对象在使用时应该取消 Java 语言访问检查,使得私有的构造函数能够被访问,这样使得单例模式失效。
//自认为史上最牛的单例模式的实现方式 public class Singleton { //使用LazyHolder的时候,默认会先初始化内部类 //如果没使用,则内部类不加载的 private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } // 每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个放啊不会被重写、重载 private Singleton (){ if(LazyHolder.INSTANCE!=null){ //在返回结构以前,一定会先加载内部类 throw new RuntimeException("不允许创建多个实例!"); } } // 默认不加载 public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
四、序列化破坏单例
一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象,并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于违背了单例模式。
public class SeriableSingleton implements Serializable { //序列化 //把内存中对象的状态转换为字节码的形式 //把字节码通过IO输出流,写到磁盘上 //永久保存下来,持久化 //反序列化 //将持久化的字节码内容,通过IO输入流读到内存中来 //转化成一个Java对象 public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } }
public class SeriableSingletonTest { public static void main(String[] args) { SeriableSingleton s1 = null; SeriableSingleton s2 = SeriableSingleton.getInstance(); FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (SeriableSingleton)ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } } }
从运行结果可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例设计模式的初衷。那么,我们如何保证在序列化的情况下,也能够实现单例模式呢,其实很简单,只要增加readResolve()方法即可。
public class SeriableSingleton implements Serializable { //序列化 //把内存中对象的状态转换为字节码的形式 //把字节码通过IO输出流,写到磁盘上 //永久保存下来,持久化 //反序列化 //将持久化的字节码内容,通过IO输入流读到内存中来 //转化成一个Java对象 public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } private Object readResolve(){ return INSTANCE;} }