面试官:请写一个你认为比较“完美”的单例 (2)

使用静态变量 flag 来控制,只有从 getInstance() 调用构造器才能正常实例化,否则抛出异常。但马上小雨就发现了存在的问题:既然可以通过反射来调用构造器,那么也可以通过反射来改变 flag 的值,这样苦心设置的 flag 控制逻辑不就被打破了吗。看来也没那么“完美”。虽然并不那么完美,但也一定程度上规避了使用反射直接调用构造器的场景,并且貌似也想不出更好的办法了,于是小雨提交了答案。

面试官露出迷之微笑:“想法挺好,反射的问题基本解决了,但如果我序列化这个单例对象,然后再反序列化出来一个对象,这两个对象还一样吗,还能保证单例吗。如果不能,怎么解决这个问题?”

SerializationSafeSingleton s1 = SerializationSafeSingleton.getInstance(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(s1); oos.close(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); SerializationSafeSingleton s2 = (SerializationSafeSingleton) ois.readObject(); ois.close();

s1 == s2 吗? 答案是否,如何解决呢。

序列化安全的单例

小雨思考了一会,想起了曾经学习序列化知识时接触的 readResolve() 方法,该方法在ObjectInputStream已经读取一个对象并在准备返回前调用,可以用来控制反序列化时直接返回一个对象,替换从流中读取的对象,于是在前面实现的基础上,小雨添加了一个 readResolve() 方法,

public class Singleton { private static volatile Singleton instance; private static boolean flag = false; private Singleton(){ synchronized (Singleton.class) { if (flag) { flag = false; } else { throw new RuntimeException("Please use getInstance() method to get the single instance."); } } } public static final Singleton getInstance(){ if(instance == null) { synchronized (Singleton.class){ if(instance == null) { flag = true; instance = new Singleton(); } } } return instance; } /** * 该方法代替了从流中读取对象 * @return */ private Object readResolve(){ return getInstance(); } }

通过几个步骤的逐步改造优化,小雨完成了一个基本具备线程安全、反射安全、序列化安全的单例实现,心想这下应该足够完美了吧。面试官脸上继续保持着迷之微笑:“这个实现看起来还是显得有点复杂,并且也不能完全解决反射安全的问题,想想看还有其它实现方案吗。”

其它方案

小雨反复思考,前面的实现是通过加锁来实现线程安全,除此之外,还可以通过类的加载机制来实现线程安全——类的静态属性只会在第一次加载类时初始化,并且在初始化的过程中,JVM是不允许其它线程来访问的,于是又写出了下面两个版本

1.静态初始化版本

public class Singleton { private static final Singleton instance = new Singleton(); private Singleton(){} public static final Singleton getInstance() { return instance; } }

该版本借助JVM的类加载机制,本身线程安全,但只要 Singleton 类的某个静态对象(方法或属性)被访问,就会造成实例的初始化,而该实例可能根本不会被用到,造成资源浪费,另一方面也存在反射与序列化的安全性问题,也需要进行相应的处理。

2.静态内部类版本

public class Singleton { private Singleton(){} public static final Singleton getInstance() { return SingletonHolder.instance; } private static class SingletonHolder { private static final Singleton instance = new Singleton(); } }

该版本只有在调用 getInstance() 才会进行实例化,即延迟加载,避免资源浪费的问题,同时也能保障线程安全,但是同样存在反射与序列化的安全性问题,需要相应处理。

这貌似跟前面版本的复杂性差不多啊,依然都需要解决反射与安全性的问题,小雨心想,有没有一种既简单又能避免这些问题的方案呢。

“完美”方案

一阵苦思冥想之后,小雨突然脑中灵光闪现,枚举!(这也是《Effective Java》的作者推荐的方式啊)

public enum Singleton { INSTANCE; public void func(){ ... } }

可以直接通过 Singleton.INSTANCE 来引用单例,非常简单的实现,并且既是线程安全的,同时也能应对反射与序列化的问题,面试官想要的估计就是它了吧。小雨再次提交了答案,这一次,面试官脸上的迷之微笑逐渐消失了……

Tips:为什么枚举是线程、反射、序列化安全的?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspwxz.html