哪些情况下的单例对象可能会破坏?

简介: 有位小伙伴在评论区留言,希望我分享一些设计模式相关的面试题。设计模式本身是很抽象的,但是在很多面试中又经常被问到,很多小伙伴其实都能答得上,但是又不知道怎么样回答才能让面试官满意,往往越简单的知识越能够体现出核心竞争力。

【Java面试】一道简单又不简单的面试题,哪种情况下的单例对象可能会被破坏?

有位小伙伴在评论区留言,希望我分享一些设计模式相关的面试题。设计模式本身是很抽象的,但是在很多面试中又经常被问到,很多小伙伴其实都能答得上,但是又不知道怎么样回答才能让面试官满意,往往越简单的知识越能够体现出核心竞争力。

今天,我给大家分享一个简单又不简单的单例模式,希望能够帮助到大家。先来看单例模式的定义。

1、单例模式的定义

关于单例模式的定义,官方原文是这样描述的:

25ca76ac915f6bef8f38845c978688eb.png

Ensure a class has only one instance,and provide a global point of access to it.

大致意思是,确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

单例模式的写法相信只要是程序员应该都会,也很非常简单,这里我就不一一列举了。今天,我要重点要给大家分析的是,在Java中,哪些单例对象是最有可能被破坏的。

2、单例被破坏的五个场景

a1c34e7e521517f8d207e230dac44b2d.png

我把可能出现单例被破坏的情况,一共归纳为五种,分别为多线程破坏单例、指令重排破坏单例、克隆破坏单例、反序列化破坏单例、反射破坏单例。

下面我详细分析一下每种情况并给出解决方案:

第一种:多线程破坏单例

288f1362336a87f75bf2a990e80550a2.png

在多线程环境下,线程的时间片是由CPU自由分配的,具有随机性,而单例对象作为共享资源可能会同时被多个线程同时操作,从而导致同时创建多个对象。当然,这种情况只出现在懒汉式单例中。如果是饿汉式单例,在线程启动前就被初始化了,不存在线程再创建对象的情况。

如果懒汉式单例出现多线程破坏的情况,我给出以下两种解决方案:

1、改为DCL双重检查锁的写法。

2、使用静态内部类的写法,性能更高。

第二种:指令重排破坏单例

0b18609cd5f6c9c2ca653990f833ec04.png

指令重排也可能导致懒汉式单例被破坏。来看这样一句代码:

instance = new Singleton();

看似简单的一段赋值语句:instance = new Singleton();

其实JVM内部已经被转换为多条执行指令:

memory = allocate();   分配对象的内存空间指令

ctorInstance(memory);   初始化对象

instance = memory;     将已分配存地址赋值给对象引用

1、分配对象的内存空间指令,调用allocate()方法分配内存。  

2、调用ctorInstance()方法初始化对象    

3、将已分配存地址赋值给对象引用

但是经过重排序后,执行顺序可能是这样的:

memory = allocate();   分配对象的内存空间指令

instance = memory;     将已分配存地址赋值给对象引用

ctorInstance(memory);   初始化对象

1、分配对象的内存空间指令

2、设置instance指向刚分配的内存地址

3、初始化对象

我们可以看到指令重排之后,instance指向分配好的内存放在了前面,而这段内存的初始化的指令被排在了后面,在线程 T1 初始化完成这段内存之前,线程T2 虽然进不去同步代码块,但是在同步代码块之前的判断就会发现 instance 不为空,此时线程T2 获得 instance 对象,如果直接使用就可能发生错误。

如果出现这种情况,我该如何解决呢?只需要在成员变量前加volatile,保证所有线程的可见性就可以了。

private static volatile Singleton instance = null;

第三种:克隆破坏单例

b9ec876c0a4faebc2ce76c3f60f26a82.png

在Java中,所有的类就继承自Object,也就是说所有的类都实现了clone()方法。如果是深clone(),每次都会重新创建新的实例。那如果我们定义的是单例对象,岂不是也可调用clone()方法来反复创建新的实例呢?确实,这种情况是有可能发生的。为了避免发生这样结果,我们可以在单例对象中重写clone() 方法,将单例自身的引用作为返回值。这样,就能避免这种情况发生。

第四种:反序列化破坏单例

525f4ff2fc2cf20582a952251d909365.png

我们将Java对象序列化以后,对象通常会被持久化到磁盘或者数据库。如果我们要再次加载到内存,就需要将持久化的内容反序列化成Java对象。反序列化是基于字节码来操作的,我们要序列化以前的内容进行反序列化到内存,就需要重新分配内存,也就是说,要重新创建对象。那如果要反序列化的对象恰恰是单例对象,我们该怎么办呢?

我告诉大家一种解决方案,在反序列的过程中,Java API会调用readResolve()方法,可以通过获取readResolve()方法的返回值覆盖反序列化创建的对象。

(反序列化对象 指向  单例对象动画 出现 )  

因此,只需要重写readResolve()方法,将返回值设置为已经存在的单例对象,就可以保证反序列化以后的对象是同一个了。之后再将反序列化后的对象中的值,克隆到单例对象中。

第五种:反射破坏单例

9379a90aa8cc9f6c359e7ed8fa159e09.png

以上讲的所有单例情况都有可能被反射破坏。因为Java中的反射机制是可以拿到对象的私有的构造方法,也就是说,反射可以任意调用私有构造方法创建单例对象。当然,没有人会故意这样做,但是如果出现意外的情况,该如何处理呢?我推荐大家两种解决方案,

第一种方案是在所有的构造方法中第一行代码进行判断,检查单例对象是否已经被创建,如果已经被创建,则抛出异常。这样,构造方法将会被终止调用,也就无法创建新的实例。

第二种方案,将单例的实现方式改为枚举式单例,因为在JDK源码层面规定了,不允许反射访问枚举。

3、总结

最后总结一下:

99025414e2de498a9c94401f5caa24d1.png

1、在所有单例写法中,如果程序不是太复杂,单例对象又不多,推荐使用饿汉式单例。

2、但如果经常发生多线程并发情况下,推荐使用静态内部类和枚举式单例,我的《设计模式就该这样学》这本书中,也推荐这样的写法。

听懂的小伙伴,请关注点个赞,下次不迷路。

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

相关文章
|
11月前
|
Web App开发 监控 网络协议
网络分析与监控:阿里云拨测方案解密
阿里云网络拨测业务提供了全球、多种协议、多种网络态势的用户网络性能和用户体验监控场景的全面可观测方案。该文章从拨测场景下,介绍了用户如何快速的构建一套全球用户视角的服务可用性大盘,为客户的业务保驾护航。
1403 164
|
关系型数据库 MySQL Linux
Linux下查看软件安装与安装路径
Linux下查看软件安装与安装路径
1770 0
|
编译器 C++
C++ 字符串转浮点数,包括整数、小数和科学记数法
C++ 字符串转浮点数,包括整数、小数和科学记数法
1176 0
|
UED
易用性测试小结
易用性测试小结
930 0
易用性测试小结
|
Android开发 数据格式 XML
【Android 进阶】仿抖音系列之列表播放视频(二)
上一篇中,我们实现了仿抖音上下翻页切换视频的效果,详见【Android 进阶】仿抖音系列之翻页上下滑切换视频(一),这一篇,我们来实现抖音列表播放视频。 之前也在github上找到一个demo,这是链接,原理和我的一样,只是用起来比较麻烦。
3785 0
|
4天前
|
云安全 监控 安全
|
2天前
|
存储 机器学习/深度学习 人工智能
打破硬件壁垒!煎饺App:强悍AI语音工具,为何是豆包AI手机平替?
直接上干货!3000 字以上长文,细节拉满,把核心功能、使用技巧和实测结论全给大家摆明白,读完你就知道这款 “安卓机通用 AI 语音工具"——煎饺App它为何能打破硬件壁垒?它接下来,咱们就深度拆解煎饺 App—— 先给大家扒清楚它的使用逻辑,附上“操作演示”和“🚀快速上手不踩坑 : 4 条核心操作干货(必看)”,跟着走零基础也能快速上手;后续再用真实实测数据,正面硬刚煎饺 App的语音助手口令效果——创建京东「牛奶自动下单神器」口令 ,从修改口令、识别准确率到场景实用性,逐一测试不掺水,最后,再和豆包 AI 手机语音助手的普通版——豆包App对比测试下,简单地谈谈煎饺App的能力边界在哪?
|
9天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1151 7
|
11天前
|
机器学习/深度学习 人工智能 数据可视化
1秒生图!6B参数如何“以小博大”生成超真实图像?
Z-Image是6B参数开源图像生成模型,仅需16GB显存即可生成媲美百亿级模型的超真实图像,支持中英双语文本渲染与智能编辑,登顶Hugging Face趋势榜,首日下载破50万。
732 42

热门文章

最新文章