第一时间获取技术干货和业界资讯!

单例模式实际上也不止 7 种。但是,每一种都并非安全的。今天我给大家讲一讲如何利用克隆、序列化、反射机制破坏单例模式。

我今天以痴汉式单例为例来讲,其他的单例模式破坏方式类似。

上面这个单例实现,看似很完美。但我们通过克隆、序列化、反射机制,来击破这个单例模式。

创建一个 Java 对象一般有 4 种方式:new 、克隆、序列化、反射!现在 new 这种方式不能使用了,那我们还可以使用剩下的 3 种方式!

先来看克隆!

实现 Cloneable 接口,尽管构造函数是私有,但还会创建一个对象。因为 clone 方法不会调用构造函数,会直接从内存中 copy 内存区域。所以单例模式的类是切记不要实现 Cloneable 接口。

自己运行一下,hash 值不一样,所以克隆成功了,生成了一个新对象。单例模式被成功破坏!

那么怎么抵制被克隆呢?

就是重写 clone 方法,调用 getInstance() 方法,返回已有的实例即可!

现在我们再来看序列化是如何破坏单例模式的。现在假设你的单例模式,实现了 Serializable 接口。看我下面反序列化的案例!

执行之后,hash 值不一样了,获取的对象非同一对象。结论,单例模式又被破坏了!那么怎么防止被反序列化呢?

很简单,自定义实现对象的 readResolve() 方法。

为什么实现对象的 readResolve() 方法就可以了呢?这个你可以自己 debug 一下,上面反序列化的代码。其中有一个 readOrdinaryObject 方法在做怪!

关键代码都注射的比较全,我相信你能看明白。如果还不明白,加我微信ID:xttblog。

最后,我们再来看反射是如何破坏单例模式的!

执行之后,hash 值不一样了,获取的对象非同一对象。结论,单例模式又被破坏了!那么如何解决呢?很简单,加入下面的代码。

因为执行反射会调用无参构造函数,所以上面的判断就可以起作用了!

综上所述,单例模式需要考虑,线程安全问题,效率问题,防止反射、反序列化、克隆。要不然,就有可能被***利用!

看到这里,有些人可能会问,这也太麻烦了,有没有更简便的方法呢?有,枚举模式。枚举类型是绝对单例的,可以无责任使用。

一个枚举,就算实现双接口,也是无论如何都无法被破坏的。枚举无法克隆,没有这样的方法。没有构造函数,会抛出异常。就算你在枚举里加了构造函数,也是一样的。对于反序列化 Java 仅仅是将枚举对象的 name 属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf 方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法。所以,枚举才是实现单例模式的最好方式!