分享一个我遇到过的“量子力学”级别的BUG。
你好呀,我是歪歪。
前几天在网上冲浪的时候,看到知乎上的这个话题:

一瞬间,一次历史悠久但是记忆深刻的代码调试经历,“刷”的一下,就在我的脑海中蹦出来了。
虽然最终定位到的原因令人无语,对于日常编码也没啥帮助,但是真的是:

情景再现
我记得当时我是学习 ConcurrentLinkedQueue (下文用 CLQ 代替)的这个玩意,为了比较深入的掌握这个玩意,我肯定是要 Debug 跟踪一下源码的。
问题就出现在 Debug 的时候,现象非常诡异,听我细细道来。
首先,我当时的 Demo 极其简单,就这么两行代码:

new 一个 CLQ 对象,然后调用 offer 方法筛一个对象进去。
完事了。
这么简单的代码能搞出什么牛逼的玩意呢?
首先,我带你看看 CLQ 的数据结构。
CLQ 是由一个个 Node 组成的链式结构。

new CLQ 的时候通过 new Node() 构造出一个特殊的“dummy node”,翻译过来大家一般叫它“哑元节点”。
然后将头指针 head 和尾指针 tail 都指向这个哑元节点。
那这个 Node 长啥样呢?
Node 里面有一个 item(放的是存储的对象),还有一个 next 节点(指向的是当前 Node 的下一个节点):

从数据结构来看,也知道这是一个单向链表了。
当时为了学它,我想通过日志的方式直接输出链表结构,这应该是最简单的演示方式了。
毕竟 Java 程序员,就靠日志活着了。
所以我当时自定义了一个 WhyConcurrentLinkedQueue(下文简写为 WhyCLQ)。
这个 WhyCLQ 是怎么来的呢?
非常简单,我直接把 JDK 源码中的 CLQ 复制出来一份,改名为 WhyCLQ 就完事了。

然后搞个测试用例跑跑:

非常 nice,没有任何毛病。
我们现在可以任意的在代码中增加输出日志了。
比如,我想要看 WhyCLQ 这个链式结构到底是怎么样的。
我们可以在自定义的 CLQ 里面加一个打印链表结构的方法:
public void printWhyCLQ() {
StringBuilder sb = new StringBuilder();
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item;
sb.append(item).append("->");
}
System.out.println("链表item对象指向 =" + sb);
}
然后在每次 offer 方法新增完成后,调用一下 printWhyCLQ 方法,输出当前的链式结构:

其他的地方类似,只要你觉得源码看起来有点绕的地方,你就可以加输出语句,哪怕一行代码就配上一行输出语句也没问题。
甚至,你还能“客制化”源码,但是这不是本文的重点,我就不展开了。
通过复制源码的方式自定义一个 JDK 源码中的类,然后加上大量的输出语句,有时候也会对源码进行各种改装,是我常用的一个学习小技巧,分享给你,不用客气。
当你被一步步 debug 带晕的时候,你可以试一试这种方式,先整体再局部。
好,到这里就算是铺垫完成了。
我们回到最开始的这两行代码:

按照我们的理解,第一次 offer 之后,对应的链表画个简图应该是这样的:

但是最后的输出是这样的:

为什么输出的日志不是 null->@4629104a 呢?
因为我们自定义的 printWhyCLQ 这个方法里面会调用 first 方法,获取真正的头节点,即 item 不为 null 的节点:

也就是我框起来的地方:first 方法中的 updateHead(h, p) 方法,会去修改头结点。
然后,我还想在第一次 offer 的时候,详细的输出头结点的信息,所以加了这几行输出语句:

直接把程序跑起来,对应的效果是这样的:

但是,当我在这个分支入口,打上断点,用 debug 模式进行调试的时候:

运行结果是这样的:

空指针异常!!!???
为了让你有更加直观的感受,我给你上个动图。
首先,是直接把程序运行起来的动图:

这是 Debug 运行时的动图:

如果前面的文字你没看懂,不重要,你只需要记住下面这个现象:
同样的程序,当你直接运行,就能正常结束,当你用 Debug 模式运行的时候,就会抛出空指针异常。
来,如果是你遇到这个问题,你会怎么办?
当年我还是一个萌新菜鸟的时候,遇到这个问题,直接就懵逼了啊,百思不得其解,感觉编程的大厦正在摇摇欲坠。
这真的就很诡异啊!

当你直接运行程序,会拿到一个预期的结果。
但是试图通过 Debug 模式去观察这个程序的时候,这个程序就会抛出异常。
这很难不让人想起“量子力学”中的光的双缝干涉试验啊。
观测手段触发了光的粒子状态,所以没有干涉条纹。
如果不观测,光就是波的形态,出现了干涉条纹。
如果你不知道我在说什么,一点也不重要。
但是你知道我在说什么,你就知道,歪师傅这个程序的现象,用“量子力学”来形容是多么的贴切。
我甚至还怀疑过是质子,一定是质子在搞事情。

当时,我是怎么解决这个问题的呢?
没有解决。
当年经验浅薄,现象又太过诡异导致我不知道应该怎么去解决,而且最重要的是并没有影响我理解 CLQ 这个玩意。
是的,感谢我当时还记得主要目标是去学习 CLQ,而不是去研究这个诡异的现象。
偶遇真相
我忘了隔了多长时间,只记得是一个麦子黄了的季节,我在这个链接中偶遇到了真相:
https://stackoverflow.com/questions/55889152/why-my-object-has-been-changed-by-intellij-ideas-debugger-soundlessly

这个哥们遇到的问题和我一模一样,但是这个问题下面只有一个回答:

这个回答给出的解决方案
最后的解决方案就是关闭 IDEA 的这两个配置,他们默认是开启的:

当关闭这两个配置后,我的程序在 Debug 的时候也正常了。
为什么呢?
因为 IDEA 在 Debug 模式下会主动的帮我们调用一次 toString 方法。
而在 CLQ 的 toString 方法里面,会去调用 first 方法:

前面我说了:first 方法中的 updateHead(h, p) 方法,会去修改头结点。

之前我给的简图是这样的:

由于 Debug 会调用 toString 方法,从而触发了 first 方法,进而导致了头结点不是 null,而是这个 obj 了:

再到 this.head.next 这里获取头结点的 next 的时候,由于 next 并不存在,值为 null:

所以 this.head.next.item 抛出了空指针异常。
没有什么玄学,我们要相信科学。
但是,这个真相确实有点坑。

IDEA 图啥?
那么问题就来了。
为什么 IDEA 要在 Debug 的时候默认调用一下 toString 方法呢?
我用 HashMap 举例,给你上个对比图你就知道它想要干啥了。
这是默认配置的情况:

可以直观的看到 map 中 key 和 value 的情况
当我们取消前面说的配置:

再次 Debug 的时候,看到的就是这样的:

而且可以看到,toString 方法是可以点击的。
当你点击之后,就变成了这样:

这么一对比,就很直观了。
你说 IDEA 图啥?
还不就说图用户调试起来的时候,看起来更加直观嘛,确实是一片好心。
谁能想到你 toString 方法中还能藏着一些逻辑呢。
这波我站 IDEA。
又学到一个埋坑的小技巧
通过前面的介绍,我仿佛又掌握了一个埋坑的小技巧。
我给你演示一下。
首先我定义一个 why 的类:

这个类的 toSting 方法中有 age++ 这样的操作。
当你直接运行这个程序的时候,运行结果为 18:

但是,当你 Debug 的时候:

age 就变成 19 了。
而且是看一次,就涨一岁,这你受得了吗:

如果代码再复杂一点,找问题都让你焦头烂额了。
谁能想到 IDEA 在你 Debug 的时候帮你调用了 toString,谁又能想到 toString 方法中还有逻辑呢?
如果 toString 方法中的逻辑,和前面说的 CLQ 一样,会影响到你要寻找的答案...
这一套丝滑小连招下来,你就玩去吧。
一个埋坑的小技巧,没到血海深仇,不要轻易使用。
最后,你说上帝在编程的时候,会不会也是埋了这样的一个坑。
当我们直接运行“光”这个方法的时候,光就是波的形态。
但是当我们使用通过观察手段去 Debug “光”这个方法到底是怎么运行的时候,上帝他老人家就会在“光的 toStirng 方法”中主动调用一个让光变成粒子的逻辑。
所以,我们的任何观测手段都会触发这个“光的 toStirng 方法”,导致光的出现了粒子状态,在光的双缝干涉试验直接中,就没有出现干涉条纹。
从编程角度,看量子力学,有点意思。

分享一个我遇到过的“量子力学”级别的BUG。的更多相关文章
- 分享一个新出炉的JVM里不痛不痒的BUG(Attach机制相关)
本文来自: PerfMa技术社区 PerfMa(笨马网络)官网 概述 老早之前写过一篇文章,关于attach机制的,可以看下这篇老文章了解一下JVM源码分析之Attach机制实现完全解读,比如大家常用 ...
- android:分享 一个很强大的LOG开关---Log.isLoggable
标签:android分享 一个很强大的log开 1.API亮点: 此API可以实现不更换APK,在出问题的手机上就直接能抓到有效log,能提升不少工作效率. 2.API介绍 最近在解决短信问题时,看到 ...
- 分享一个大型进销存供应链项目(多层架构、分布式WCF多服务器部署、微软企业库架构)
项目源码下载: WWW.DI81.COM 分享一个大型进销存供应链项目(多层架构.分布式WCF多服务器部署.微软企业库架构) 这是一个比较大型的项目,准备开源了.支持N家门店同时操作.远程WCF+企 ...
- 分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)
分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...
- 分享一个MySQL分库分表备份脚本(原)
分享一个MySQL分库备份脚本(原) 开发思路: 1.路径:规定备份到什么位置,把路径(先判断是否存在,不存在创建一个目录)先定义好,我的路径:/mysql/backup,每个备份用压缩提升效率,带上 ...
- 分享一个与ABP配套使用的代码生成器源码
点这里进入ABP系列文章总目录 分享一个与ABP配套使用的代码生成器源码 真对不起关注我博客的朋友, 因最近工作很忙, 很久没有更新博客了.以前答应把自用的代码生成器源码共享出来, 也一直没有时间整理 ...
- 分享一个常用Adb命令
分享一个常用Adb命令 首先 首先感谢@xuxu的常用adb命令,收益良多,但是已经不能满足于我,所以补充了下. 再者 好久没发帖了,最近论坛老司机们都在讨论/总结,我就用这个干货回报吧. 最后 基于 ...
- 福利到~分享一个基于jquery的智能提示控件intellSeach.js
一.需求 我们经常会遇到[站内搜索]的需求,为了提高用户体验,我们希望能做到像百度那样的即时智能提示.例如:某公司人事管理系统,想搜索李XX,只要输入“李”,系统自然会提示一些姓李的员工,这样方便用户 ...
- 分享一个oraclehelper
分享一个拿即用的oraclehelper 首先要引用本机中的oralce access,如果是64位的话,也必须是64位运行,不然会报连接为空connection 等于null. using Orac ...
- 分享一个ruby网站 | 菜鸟教程
http://www.runoob.com/ruby/ruby-tutorial.html 分享一个ruby网站.
随机推荐
- IntelliJ IDEA安装与配置(支持最新2020.2)
前言 我是从eclipse转IDEA的,对于习惯了eclipse快捷键的我来说,转IDEA开始很不习惯,IDEA快捷键多,组合多,记不住,虽然可以设置使用eclipse的快捷键,但是总感觉怪怪的.开始 ...
- vmstorage如何将原始指标转换为有组织的历史
vmstorage如何将原始指标转换为有组织的历史 参考自:vmstorage-how-it-handles-data-ingestion vmstorage是VictoriaMetrics中负责处理 ...
- 基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v6.2版已发布
关于MobileIMSDK MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架,超轻量级.高度提炼,一套API优雅支持UDP .TCP .WebSocket 三种协议,支持iOS.A ...
- KMS for Office 2024
I. 镜像下载 官方镜像下载地址: Office 2024 专业增强版: https://officecdn.microsoft.com/db/492350f6-3a01-4f97-b9c0-c7c6 ...
- Solution -「CF 590E」Birthday
\(\mathscr{Description}\) Link. 给定 \(n\) 个字符串 \(S_{1..n}\),选出其一个最大子集 \(T\),使得 \(T\) 中的字符串两两不存在包含 ...
- G1原理—3.G1是如何提升垃圾回收效率
大纲 1.G1为了提升GC的效率设计了哪些核心机制 2.G1中的记忆集是什么 3.G1中的位图和卡表 4.记忆集和卡表有什么关系 5.RSet记忆集是怎么更新的 6.DCQ机制的底层原理是怎样的 7. ...
- JS 实现在指定的时间点播放列表中的视频
为了实现在指定的时间点播放列表中的视频,你可以使用JavaScript中的setTimeout或setInterval结合HTML5的<video>元素.但是,由于你需要处理多个时间点,并 ...
- 2020年最新网络编程面试题-copy
计算机网络体系结构 在计算机网络的基本概念中,分层次的体系结构是最基本的.计算机网络体系结构的抽象概念较多,在学习时要多思考.这些概念对后面的学习很有帮助. 网络协议是什么? 在计算机网络要做到有条不 ...
- linux:安装php7.x
参考:链接 更新yum源 CentOS/RHEL 7.x: rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n ...
- Access pg walkthrough Intermediate window域渗透
namp nmap -p- -A -sS -T4 192.168.200.187 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-23 00 ...