然后,判断 rep 是否和 obj 相等 。 obj 是刚才我们通过构造函数创建出来的新对象,而由于我们重写了 readResolve 方法,直接返回了单例对象,因此 rep 就是原来的单例对象,和 obj 不相等。
于是,把 rep 赋值给 obj ,然后返回 obj。
所以,最终得到这个 obj 对象,就是我们原来的单例对象。
至此,我们就明白了是怎么一回事。
一句话总结就是:当从对象流 ObjectInputStream 中读取对象时,会检查对象的类否定义了 readResolve 方法。如果定义了,则调用它返回我们想指定的对象(这里就指定了返回单例对象)。
总结因此,完整的 DCL 就可以这样写,
public class Singleton implements Serializable { //注意,此变量需要用volatile修饰以防止指令重排序 private static volatile Singleton singleton = null; private Singleton(){ if(singleton != null){ throw new RuntimeException("Can not do this"); } } public static Singleton getInstance(){ //进入方法内,先判断实例是否为空,以确定是否需要进入同步代码块 if(singleton == null){ synchronized (Singleton.class){ //进入同步代码块时再次判断实例是否为空 if(singleton == null){ singleton = new Singleton(); } } } return singleton; } // 定义readResolve方法,防止反序列化返回不同的对象 private Object readResolve(){ return singleton; } }另外,不知道细心的读者有没有发现,在看源码中 switch 分支有一个 case TC_ENUM 分支。这里,是对枚举类型进行的处理。
感兴趣的小伙伴可以去研读一下,最终的效果就是,我们通过枚举去定义单例,就可以防止序列化破坏单例。