面试官看完我手写的单例直接惊呆了!

单例模式应该算是 23 种设计模式中,最常见最容易考察的知识点了。经常会有面试官让手写单例模式,别到时候傻乎乎的说我不会。

之前,我有介绍过单例模式的几种常见写法。还不知道的,传送门看这里:

设计模式之单例模式

本篇文章将展开一些不太容易想到的问题。带着你思考一下,传统的单例模式有哪些问题,并给出解决方案。让面试官眼中一亮,心道,小伙子有点东西啊!

以下,以 DCL 单例模式为例。

DCL 单例模式

DCL 就是 Double Check Lock 的缩写,即双重检查的同步锁。代码如下,

public class Singleton { //注意,此变量需要用volatile修饰以防止指令重排序 private static volatile Singleton singleton = null; private Singleton(){ } public static Singleton getInstance(){ //进入方法内,先判断实例是否为空,以确定是否需要进入同步代码块 if(singleton == null){ synchronized (Singleton.class){ //进入同步代码块时再次判断实例是否为空 if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }

乍看,以上的写法没有什么问题,而且我们确实也经常这样写。

但是,问题来了。

DCL 单例一定能确保线程安全吗?

有的小伙伴就会说,你这不是废话么,大家不都这样写么,肯定是线程安全的啊。

确实,在正常情况,我可以保证调用 getInstance 方法两次,拿到的是同一个对象。

但是,我们知道 Java 中有个很强大的功能——反射。对的,没错,就是他。

通过反射,我就可以破坏单例模式,从而调用它的构造函数,来创建不同的对象。

public class TestDCL { public static void main(String[] args) throws Exception { Singleton singleton1 = Singleton.getInstance(); System.out.println(singleton1.hashCode()); // 723074861 Class<Singleton> clazz = Singleton.class; Constructor<Singleton> ctr = clazz.getDeclaredConstructor(); //通过反射拿到无参构造,设为可访问 ctr.setAccessible(true); Singleton singleton2 = ctr.newInstance(); System.out.println(singleton2.hashCode()); // 895328852 } }

我们会发现,通过反射就可以直接调用无参构造函数创建对象。我管你构造器是不是私有的,反射之下没有隐私。

打印出的 hashCode 不同,说明了这是两个不同的对象。

面试官看完我手写的单例直接惊呆了!

那怎么防止反射破坏单例呢?

很简单,既然你想通过无参构造来创建对象,那我就在构造函数里多判断一次。如果单例对象已经创建好了,我就直接抛出异常,不让你创建就可以了。

修改构造函数如下,

面试官看完我手写的单例直接惊呆了!

再次运行测试代码,就会抛出异常。

面试官看完我手写的单例直接惊呆了!

有效的阻止了通过反射去创建对象。

那么,这样写单例就没问题了吗?

这时,机灵的小伙伴肯定就会说,既然问了,那就是有问题(可真是个小机灵鬼)。

但是,是有什么问题呢?

我们知道,对象还可以进行序列化反序列化。那如果我把单例对象序列化,再反序列化之后的对象,还是不是之前的单例对象呢?

实践出真知,我们测试一下就知道了。

// 给 Singleton 添加序列化的标志,表明可以序列化 public class Singleton implements Serializable{ ... //省略不重要代码 } //测试是否返回同一个对象 public class TestDCL { public static void main(String[] args) throws Exception { Singleton singleton1 = Singleton.getInstance(); System.out.println(singleton1.hashCode()); // 723074861 //通过序列化对象,再反序列化得到新对象 String filePath = "D:\\singleton.txt"; saveToFile(singleton1,filePath); Singleton singleton2 = getFromFile(filePath); System.out.println(singleton2.hashCode()); // 1259475182 } //将对象写入到文件 private static void saveToFile(Singleton singleton, String fileName){ try { FileOutputStream fos = new FileOutputStream(fileName); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(singleton); //将对象写入oos oos.close(); } catch (IOException e) { e.printStackTrace(); } } //从文件中读取对象 private static Singleton getFromFile(String fileName){ try { FileInputStream fis = new FileInputStream(fileName); ObjectInputStream ois = new ObjectInputStream(fis); return (Singleton) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } }

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

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