量子力学中是否存在难以解释的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 里面加一个打印链表结构的方法:
publicvoidprintWhyCLQ(){
StringBuildersb=newStringBuilder();
for(Node<E>p=first();p!=null;p=succ(p)){
Eitem=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 模式运行的时候,就会抛出空指针异常。
来,如果是你遇到这个问题,你会怎么办?
当年我还是一个萌新菜鸟的时候,遇到这个问题,直接就懵逼了啊,百思不得其解,感觉编程的大厦正在摇摇欲坠。
这真的就很诡异啊!
当你直接运行程序,会拿到一个预期的结果。
