博客
关于我
如何破坏单例?我说了好几种方式,面试官:没想到你真会
阅读量:78 次
发布时间:2019-02-25

本文共 4364 字,大约阅读时间需要 14 分钟。

作者:面试现场 来自:程序员面试现场 

 

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。是一种创建型设计模式。他的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式一般体现在类声明中,单例的类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

但是其实,单例并不是完完全全安全的,也是有可能被破坏的。

以下,是一次面试现场的还原,之所以会聊到这个话题,是因为面试官问了我很多关于单例模式的问题,我回答的还可以,之后面试官随口问了一句"单例绝对安全吗?",紧接着发生了如下对话:

Q:单例模式绝对安全吗?

A:(这个问题我知道,别想难倒我)不一定的,其实单例也是有可能被破坏的?

Q:哦?怎么说?

A:单例模式其实是对外隐藏了构造函数,保证用户无法主动创建对象。但是实际上我们是有办法可以破坏他的。

Q:那你知道有什么办法可以破坏单例吗??

A:有一个比较简单的方式,那就是反射。

反射破坏单例

 

我们先来一个比较常见的单例模式:

import java.io.Serializable;/** * 使用双重校验锁方式实现单例 */public class Singleton implements Serializable{    private volatile static Singleton singleton;    private Singleton (){}    public static Singleton getSingleton() {        if (singleton == null) {            synchronized (Singleton.class) {                if (singleton == null) {                    singleton = new Singleton();                }            }        }        return singleton;    }}

这个单例模式提供了一个private类型的构造函数,正常情况下,我们无法直接调用对象的私有方法。但是反射技术给我们提供了一个后门。

如下代码,我们通过反射的方式获取到Singleton的构造函数,设置其访问权限,然后通过该方法创建一个新的对象:

import java.lang.reflect.Constructor;public class SingletonTest {    public static void main(String[] args) {        Singleton singleton = Singleton.getSingleton();        try {            Class
singleClass = (Class
)Class.forName("com.dev.interview.Singleton"); Constructor
constructor = singleClass.getDeclaredConstructor(null); constructor.setAccessible(true); Singleton singletonByReflect = constructor.newInstance(); System.out.println("singleton : " + singleton); System.out.println("singletonByReflect : " + singletonByReflect); System.out.println("singleton == singletonByReflect : " + (singleton == singletonByReflect)); } catch (Exception e) { e.printStackTrace(); } }}

输出结果为:

singleton : com.dev.interview.Singleton@55d56113singletonByReflect : com.dev.interview.Singleton@148080bbsingleton == singletonByReflect : false

如上,通过发射的方式即可获取到一个新的单例对象,这就破坏了单例。

Q:那这种破坏单例的情况,有办法避免吗?

A:其实是可以的,只要我们在构造函数中加一些判断就行了。

如下方式,我们在Singleton的构造函数中增加如下代码:

 

private Singleton() {    if (singleton != null) {        throw new RuntimeException("Singleton constructor is called... ");    }}

 

这样,在通过反射调用构造方法的时候,就会抛出异常:

Caused by: java.lang.RuntimeException: Singleton constructor is called...

序列化破坏单例

Q:嗯嗯,挺不错的,那我们换个问题吧。

A:(这部分面试官在犹豫问我什么问题,我主动提醒了他一句)其实,除了反射可以破坏单例,还有一种其他方式也可以的。

Q:嗯,那你就说说还有什么方式吧

A:其实通过序列化+反序列化的方式也是可以破坏单例的。

如以下代码,我们通过先将单例对象序列化后保存到临时文件中,然后再从临时文件中反序列化出来:

public class SingletonTest {    public static void main(String[] args) {        Singleton singleton = Singleton.getSingleton();        //Write Obj to file        ObjectOutputStream oos = null;        try {            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));            oos.writeObject(singleton);            //Read Obj from file            File file = new File("tempFile");            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));            Singleton singletonBySerialize = (Singleton)ois.readObject();            //判断是否是同一个对象            System.out.println("singleton : " + singleton);            System.out.println("singletonBySerialize : " + singletonBySerialize);            System.out.println("singleton == singletonBySerialize : " + (singleton == singletonBySerialize));        } catch (Exception e) {            e.printStackTrace();        }    }}

 

输出结果如下:

singleton : com.dev.interview.Singleton@617faa95singletonBySerialize : com.dev.interview.Singleton@5d76b067singleton == singletonBySerialize : false

 

如上,通过先序列化再反序列化的方式,可获取到一个新的单例对象,这就破坏了单例。

因为在对象反序列化的过程中,序列化会通过反射调用无参数的构造方法创建一个新的对象,所以,通过反序列化也能破坏单例。

Q:那这种破坏单例的情况,也同样有办法避免吗?

A:当然也有了。只要修改下反序列化策略就好了。

只需要在Sinleton中增加readResolve方法,并在该方法中指定要返回的对象的生成策略几可以了。即序列化在Singleton类中增加以下代码即可:

private Object readResolve() {    return getSingleton();}

Q:为什么增加readResolve就可以解决序列化破坏单例的问题了呢?

A:因为反序列化过程中,在反序列化执行过程中会执行到ObjectInputStream#readOrdinaryObject方法,这个方法会判断对象是否包含readResolve方法,如果包含的话会直接调用这个方法获得对象实例。

如何破坏单例?我说了好几种方式,面试官:没想到你真会

 

Q:那如果没有readResolve方法的话,反序列化的时候会怎么创建对象呢?

A:当然也是反射咯。

Q:那前面不是说使用反射的情况,直接在构造函数抛异常不就行了吗?

A:这个我还真试过,其实是不行的,反序列化使用的反射构造器和我们代码中使用反射的构造器不是同一个,反序列化用到的构造器并不会调用到我们对象中的构造函数…balabala…(我也不知道面试官听不听得懂,感觉是没听懂…)

Q:哦。OK吧,请问你什么时候可以来上班?

不久之后,我入职了这家公司,在一次和当初的面试官聊天的时候,他无意间和我说:当时我面试你的时候,关于单例的破坏那几个问题,其实最开始我只是随口一问,没想到你给我吹水了20分钟…当时我就觉得你这家伙是个可造之材。

随口一问…

转载地址:http://cns.baihongyu.com/

你可能感兴趣的文章
NIFI1.21.0_Mysql到Mysql增量CDC同步中_日期类型_以及null数据同步处理补充---大数据之Nifi工作笔记0057
查看>>
NIFI1.21.0_Mysql到Mysql增量CDC同步中_补充_更新时如果目标表中不存在记录就改为插入数据_Postgresql_Hbase也适用---大数据之Nifi工作笔记0059
查看>>
NIFI1.21.0_NIFI和hadoop蹦了_200G集群磁盘又满了_Jps看不到进程了_Unable to write in /tmp. Aborting----大数据之Nifi工作笔记0052
查看>>
NIFI1.21.0最新版本安装_连接phoenix_单机版_Https登录_什么都没改换了最新版本的NIFI可以连接了_气人_实现插入数据到Hbase_实际操作---大数据之Nifi工作笔记0050
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表多表增量同步_增删改数据分发及删除数据实时同步_通过分页解决变更记录过大问题_02----大数据之Nifi工作笔记0054
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表多表增量同步_插入修改删除增量数据实时同步_通过分页解决变更记录过大问题_01----大数据之Nifi工作笔记0053
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表或全表增量同步_实现指定整库同步_或指定数据表同步配置_04---大数据之Nifi工作笔记0056
查看>>
NIFI1.23.2_最新版_性能优化通用_技巧积累_使用NIFI表达式过滤表_随时更新---大数据之Nifi工作笔记0063
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_根据binlog实现数据实时delete同步_实际操作04---大数据之Nifi工作笔记0043
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_配置binlog_使用处理器抓取binlog数据_实际操作01---大数据之Nifi工作笔记0040
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_配置数据路由_实现数据插入数据到目标数据库_实际操作03---大数据之Nifi工作笔记0042
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_配置数据路由_生成插入Sql语句_实际操作02---大数据之Nifi工作笔记0041
查看>>
NIFI从MySql中离线读取数据再导入到MySql中_03_来吧用NIFI实现_数据分页获取功能---大数据之Nifi工作笔记0038
查看>>
NIFI从MySql中离线读取数据再导入到MySql中_不带分页处理_01_QueryDatabaseTable获取数据_原0036---大数据之Nifi工作笔记0064
查看>>
NIFI从MySql中离线读取数据再导入到MySql中_无分页功能_02_转换数据_分割数据_提取JSON数据_替换拼接SQL_添加分页---大数据之Nifi工作笔记0037
查看>>
NIFI从PostGresql中离线读取数据再导入到MySql中_带有数据分页获取功能_不带分页不能用_NIFI资料太少了---大数据之Nifi工作笔记0039
查看>>
nifi使用过程-常见问题-以及入门总结---大数据之Nifi工作笔记0012
查看>>
NIFI分页获取Mysql数据_导入到Hbase中_并可通过phoenix客户端查询_含金量很高的一篇_搞了好久_实际操作05---大数据之Nifi工作笔记0045
查看>>
NIFI分页获取Postgresql数据到Hbase中_实际操作---大数据之Nifi工作笔记0049
查看>>
NIFI同步MySql数据_到SqlServer_错误_驱动程序无法通过使用安全套接字层(SSL)加密与SQL Server_Navicat连接SqlServer---大数据之Nifi工作笔记0047
查看>>