当前位置: 首页 > news >正文

多线程——面试中一个常考的内容(7)

上次讲到了wait与notify,那么现在我们继续往下讲。

单例模式

这是一种设计模式,设计模式也就相当于象棋的棋谱,大佬们把一些典型的问题场景,整理出来,并且针对这些场景,代码该怎么写,具体方案给出了一些指导和建议。

而单例模式,就是设计模式中一种非常典型的模式,也是比较简单的模式,还是校招中最容易被考到的设计模式。它强制要求:某个类,在某个程序中,只有唯一一个实例(不允许创建多个实例,也就是不允许new多次)。

单例模式,强制要求一个类不能创建多个对象,这个不是口头上的协定,而是要通过机器或程序强制要求的。通过一些编程技巧,达成上述的“强制要求”,在代码中,如果创建了多个实例,就会直接编译失败。

下面是我们实现单例方式的方式:

1.饿汉方式

饿汉,即迫切,也就是很快初始化对象,静态成员的初始化,是在类加载的阶段触发的,而类加载往往就是在程序一启动就会触发。

后续统一通过getInstance这个方法来获取这里的实例。

这个私有的方法是单例方式的点睛之笔,在类外进行new操作,就会编译失败。

当然在初始化对象时,可以根据需要往括号里传参。

下图是饿汉方式的代码:

2.懒汉方式

饿和懒是相对的,饿是尽早创建实例,而懒是尽量晚的创建实例(甚至可能不创建了)。

当然,“懒”在计算机中,是褒义词,懒的另一个含义是——高效率。

为什么这么说?假设有一个很大的文件,如千万字的小说,你把编辑器打开的时候,是想把所有的内容,都从文件加载到内存中再显示,还是只把一部分内容加载并显示呢?很明显选择后者,因为前者明显卡顿,就算加载了这么多,你也看不过来的,而后者中,后续如果用户翻页,随着翻页,随时加载后续数据,就可以节省很大的空间。

懒汉模式之下,创建实例的时机,是在第一次使用的时候,而不是在程序启动的时候。

可是,咱们当前讲的这个章节叫多线程,这些与多线程有什么关系?

上述这些内容,都是铺垫,接下来才是正题。

线程安全问题

刚才编写的两份代码(饿汉、懒汉),是否是线程安全的?如果不是该怎么办?(非常经典的面试题)

对比一下这两个代码:

这里可以发现,饿汉是不会出现线程安全问题的,而真正有问题的是懒汉模式。

那么我们在多线程的情况下(t1、t2线程)拆开getInstance之后分析:

可以看到,对象instance为null,那么两个线程都会new一个新的对象,相当于创建了两次对象,即使线程调度的随机性出现的覆盖现象会使结果不变,与预期结果相同,可是new的这个对象,new的过程中,可能要把100G的数据从硬盘加载到内存,两个就是200G,也就是说,本来启动时间大约是10分钟,由于上述的bug,加载两份,导致最终时间远远超过了20分钟。

那么我们改怎么办?

观察getInstance方法,if循环里套着新的对象的初始化,虽然“=”是原子的,但是有if条件,也就是“条件修改”,就不是原子的了,于是我们希望条件和修改能够打包成原子,于是我们就想到了加锁。

1.加锁(变原子的)

当然,两个线程都加锁(两个线程加锁的对象相同),于是我们再拆开getInstance分析:

当我们加锁后,上面先执行的是抢到锁并成功加锁的线程,另一个线程就要阻塞等待,线程t1执行了if,初始化了对象之后,释放锁,然后t2加锁,此时,instance对象已经被初始化了,就不是null了,因此直接返回就可以了,不用new了,也就少new了一次。

可是,这样还有没有别的问题?

之前提到,只要加了锁就会影响执行效率。

当我们把实例创建好了之后(初始化好了),后续再调用getInstance,此时,都是直接执行return操作,如果只是进行if+return,这就是纯粹的读取操作了,该操作就不涉及线程安全问题,但是,每次调用上述方法之后,都会触发一次加锁,虽然不涉及线程安全问题了,但多线程情况下,这里的加锁,就会相互阻塞,影响线程的执行效率。

2.锁外加if条件判断来确定是否加锁——提高执行效率

最外层的if是为了判定是否需要加锁,最里层的if是为了判断是否需要new对象,这就是按需加锁,真正涉及到线程安全时再加锁,不涉及时就不加锁。

如果实例已经创建过了,就不涉及线程安全问题,反之就涉及线程安全问题。

仔细分析一下,还有什么问题?

3.指令重排序问题与volatile

可是,volatile解决的问题,更主要的是指令重排序问题。

这个也是编译器优化的一种体现形式,编译会在逻辑不变的前提下,调整你代码的执行顺序,以达到提升性能的效果。

我们看创建实例这句代码:

这个代码涉及三个步骤:

1.申请内存空间(绝对是第一步)

2.在空间上构造对象(初始化)

3.内存空间的首地址,复制给引用变量

正常来说,这三个步骤是按照123的顺序执行的,但是在指令重排序下,可能成为132这样的排序,单线程环境下,123还是132是无所谓的,但在多线程中,可能会出现bug。

32之间如果再用getInstance创建的s对象、用s来调用方法时,这个对象可能是未初始化的,而初始化操作恰好在下一步。

于是,我们给对象instance加volatile:

今天的内容就到这里,明天我们继续。

我的gitee链接:https://gitee.com/QQ2240635095/java4_6.git

http://www.jsqmd.com/news/604994/

相关文章:

  • 航海小知识 | 电子海图是什么?不止是“把图纸搬进电脑”
  • 朝闻道夕死可以(吗?
  • IIS配置HTTPS如何多个二级域名连接!
  • 如何在Dify知识库中实现多条件排除查询
  • STLink烧录器使用指南与STM单片机调试技巧
  • OpenClaw+千问3.5-35B-A3B-FP8内容处理实战:从图片识别到Markdown报告生成
  • 6款AI论文降重软件,智能改写与优化,显著提升原创度。
  • 处理通用产品时使用变量
  • Dify如何实现多轮对话记忆?
  • 2026企业媒体发稿成本管控行业洞察:找媒体发稿成本太高怎么办?邯郸市佳铭文化教你破局之道
  • 2026年四川地区消防训练箱公司TOP5推荐 附参数对比 - 优质品牌商家
  • 网卡数据处理机制与性能优化实战
  • 好用的办公家具推荐
  • aardio桌面开发实战:轻量级串口控制工具开发
  • 渗透基础知识ctfshow——Web应用安全与防护(第二章)
  • 0欧姆电阻在电子设计中的关键应用与选型指南
  • 6款AI论文改写工具,智能降重与语言润色,有效减少重复率。
  • AI率降完复测变高,不是工具问题是这个原因
  • k8s资源之StatefulSet
  • 从一次线上事故复盘:我们如何用OWASP ZAP揪出jQuery遗留的AJAX CSRF漏洞
  • DVCon 2025 论文精华导读及下载链接
  • Arduino传感器线性映射封装库:模拟信号调理与缓存优化
  • 2026最新!5款亲测好用的录音转写在线神器,免费无套路,办公学习必备真香!
  • 2026温州本地正规黄金白银回收标杆名录 附选购避坑全指南 - 优质品牌商家
  • 揭秘Apollo框架C++内存泄漏:3步定位、2分钟修复,车载系统崩溃率直降92%
  • Anomaly Detection系列(CVPR2025 LASB论文解读)
  • Dify知识库如何实现多轮对话中的情感分析
  • Redis面试问题大全,看这些就够了(凭借这个oc网易,快手)
  • G-Helper华硕优化工具终极指南:3分钟释放笔记本全部潜力
  • 解锁论文新姿势:书匠策AI,你的毕业论文“智能导航仪”!