浅谈你对单例类中使用 volatile 关键字的理解 | 超级详细,建议收藏
👨🎓作者:bug 菌
✏️博客:CSDN、掘金、infoQ、51CTO 等
🎉简介:CSDN 博客专家,C 站历届博客之星 Top50,掘金/InfoQ/51CTO 等社区优质创作者,全网粉丝合计 10w+,对一切技术感兴趣,重心偏 Java 方向;硬核公众号「 猿圈奇妙屋」,欢迎小伙伴们的加入,一起秃头,一起变强。
..
✍️温馨提醒:本文字数:3999 字, 阅读完需:约 9 分钟
如果小伙伴们在批阅文章的过程中觉得文章对自己有帮助,请别吝啬手中的赞呀,大胆的把文章点亮👍,相信你点赞了好的文章,平台也会经常给你推荐高质量好文,您的点赞三连(收藏+关注+留言)就是对 bug 菌写文道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,创作不停💕,加油☘️
一、前言🔥
环境说明:Windows10 + Idea2021.3.2 + Jdk1.8 + SpringBoot 2.3.1.RELEASE
有一期《【java 笔试题】如何手写一个单例类?》一文中我们提到,由于 volatile 关键字它可以禁止指令重排序来保证一定的有序性,故而解决了多线程情况下双重检查模式单例空指针问题。
那你们知道为何多线程下双重检查模式会导致空指针异常吗?一开始我也没多想,但是这样的学习模式是不对的,道听途说不如自己亲手试验,检验出真知,于是我花了半个小时,终于搞清楚了!
二、双重检查模式🔥
给大家再回顾一下懒汉单例之双重检查模式是如何手撕的,代码仅供参考。
看完如上单例代码实现,大佬都陷入了深思,比如我(大佬骂骂咧咧的说道:谁写的?打发要饭呢!写的啥玩意,重写)。
为什么反响会如此剧烈,有同学可能会说写的不是挺合理的嘛? 不着急,接着往下看,我会告诉你为什么。
三、案例分析🔥
我们在学习 volatile 关键字的时候,发现它可以禁止指令重排序从而保证执行有序性,等价于使用它就可以保证 new DoubleLazySingLeton()创建对象实例化过程时的顺序不变。
具体 volatile 是如何保证的呢?这就得从 volatile 关键字的源码下手了,这节我们先不深究,重点是要理解 new DoubleLazySingLeton()为何会出现不按顺序实例化的问题?而且为何要保证实例化的顺序性呢?这才是我们本文的重中之重,带着这两个问题我们接着往下看。
首先,我们都知道创建一个对象可以分为 5 步,对吧。
那 5 步呢?大家请看我画的示意图:
所以,你们可以思考一件事,如下实例化有何问题?
从表面上看,没有任何问题,但是结合双重检测模式来看,那就非常有问题了。
虽然单线程下的双重检测模式非常完美,但是在并发环境中就是 bug 般的存在。因为 new DoubleLazySingLeton()实例化它并不是一个原子操作,我们看创建对象过程也可以得知。因此我们可以把这个实例化抽象成三条 jvm 指令:如下:
上面操作 2 依赖于操作 1,但是操作 3 并不依赖于操作 2,所以 jvm 可以以“优化”为目的对它们进行重排序,经过重排序后假设顺序如下:
可以看到指令重排之后,操作 3 排在了操作 2 之前,即引用 instance 指向内存 memory 时,这段内存还没被初始化。 所以,你们发现什么问题了么?如果还没发现不着急,请接着往下看。
四、场景模拟🔥
现在我们再来模拟个场景:存在两个线程:线程 A 与线程 B,它两同时调用 getSingleton()获取 instance 对象.
然后按如下时间段进行执行,请问大家,但线程执行完会发什么?
很明显,线程 B 会访问到一个还未完成初始化的"半"个 instance 对象。为什么?当线程 A 执行到 T2 时间后已经将 instance 指向了一块内存空间,此刻线程 B 调用 getInstance(),执行到 if ( instance == null ) 语句时,instance == null 结果肯定为 false,因为 instance 已经被指定内存了不为 null,然后直接执行 return instance 语句结束,结果返回了一个没有完成初始化的“半个”单例。
也就是我一开始给大家说的指令重排之后,先执行了 A3,再执行 A2,这样本身没有问题,但如果遇到有其他线程碰巧在你指令重排后没实例化完成前调用 getInstance()获取 instance 对象,这肯定会抛异常(如上示例)。最终的结果肯定是送你 NullPointerException 大礼包。
所以对于双重检查模式下的单例而言,就会存在线程安全问题,怎么解决该线程安全,那就可以用 volatile 来保证。
顾解决线程安全的核心就是要保证 instance 对象顺序实例化,而 volatile 可以禁止指令重排序,这样尽管是多线程环境下,也不用担心 instance 实例化所带来的线程安全问题啦,这么讲,大家可得明白没有。
... ...
ok,以上就是我这期的全部内容啦,如果还想学习更多,可以看看我的往期热文推荐哦,不积跬步,无以至千里; 不积小流,无以成江海,一口吃不成一个大胖子,加油!咱们下期拜拜~~
文末🔥
安利一个超牛超硬核的专栏《springboot零基础入门教学》,此专栏包含数个完整项目从零到一的搭建,以及对 SpringBoot 入门程序原理剖析,在会用的基础上剖析源码加深理解并拓展知识点.希望能帮助到更多小伙伴们。
我是 bug 菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
感谢认真读完我博客的铁子萌,在这里呢送给大家一句话,不管你是在职还是在读,绝对终身受用。
时刻警醒自己:
抱怨没有用,一切靠自己;
想要过更好的生活,那就要逼着自己变的更强,生活加油!!!
版权声明: 本文为 InfoQ 作者【bug菌】的原创文章。
原文链接:【http://xie.infoq.cn/article/5aaf0e8205ac656cd602bb741】。文章转载请联系作者。
评论