这个Bug的排查之路,真的太有趣了。 (上)

简介: 这个Bug的排查之路,真的太有趣了。 (上)


在《深入理解Java虚拟机》一书中有这样一段代码:

public class VolatileTest {
    public static volatile int race = 0;
    public static void increase() {
        race++;
    }
    private static final int THREADS_COUNT=20;
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for(int i = 0; i < THREADS_COUNT; i++){
           new Thread(new Runnable() {
               @Override
               public void run() {
                   for (int i = 0; i < 10000; i++) {
                       increase();
                   }
               }
           }).start();
        }
        //等待所有累加线程都结束
        while(Thread.activeCount()>1)
            Thread.yield();
        System.out.println(race);
    }
}

你看到这段代码的第一反应是什么?

是不是关注点都在 volatile 关键字上。

甚至马上就要开始脱口而出:volatile 只保证可见性,不保证原子性。而代码中的 race++ 不是原子性的操作,巴拉巴拉巴拉...

反正我就是这样的:


image.jpeg


当他把代码发给我,我在 idea 里面一粘贴,然后把 main 方法运行起来后,神奇的事情出现了。

这个代码真的没有执行到输出语句,也没有任何报错。

看起来就像是死循环了一样。

不信的话,你也可以放到你的 idea 里面去执行一下。

等等......

死循环?

代码里面不是就有一个死循环吗?

//等待所有累加线程都结束
while(Thread.activeCount()>1)
    Thread.yield();

这段代码能有什么小心思呢?看起来人畜无害啊。

但是程序员的直觉告诉我,这个地方就是有问题的。

活跃线程一直是大于 1 的,所以导致 while 一直在死循环。

算了,不想了,先 Debug 看一眼吧。

Debug 了两遍之后,我才发现,这个事情,有点意思了。

因为 Debug 的情况下,程序竟然正常结束了。


image.png

啥情况啊?

分析一波走起。


为啥停不下来?


我是怎么分析这个问题的呢。

我就把程序又 Run 了起来,控制台还是啥输出都没有。

我就盯着这个控制台想啊,会是啥原因呢?

这样干看着也不是办法啊。

反正我现在就是咬死这个 while 循环是有问题的,所以为了排除其他的干扰项。

我把程序简化到了这个样子:

public class VolatileTest {
    public static volatile int race = 0;
    public static void main(String[] args) {
        while(Thread.activeCount()>1)
            Thread.yield();
        System.out.println("race = " + race);
    }
}

运行起来之后,还是没有执行到输出语句,也就侧面证实了我的想法:while 循环有问题。

而 while 循环的条件就是 Thread.activeCount()>1

朝着这个方向继续想下去,就是看看当前活跃线程到底有几个。

于是程序又可以简化成这样:

image.png

直接运行看到输出结果是 2

image.png

用 Debug 模式运行时返回的是 1。

对比这运行结果,我心里基本上就有数了。

先看一下这个 activeCount 方法是干啥的:

image.png

注意看画着下划线的地方:

返回的值是一个 estimate。

estimate 是啥?

image.png

你看,又在我这里学一个高级词汇。真是 very good。

返回的是一个预估值。

为什么呢?

因为我们调用这个方法的一刻获取到值之后,线程数还是在动态变化的。

也就是说返回的值只代表你调用的那一刻有几个活跃线程,也许当你调用完成后,有一个线程就立马嗝屁了。

所以,这个值是个预估值。

这一瞬间,我突然想到了量子力学中的测不准原理。

image.png

你不可能同时知道一个粒子的位置和它的速度,就像在多线程高并发的情况下你不可能同时知道调用 activeCount 方法得到的值和你要用这个值的时刻,这个值的真实值是多少。

你看,刚学完英语又学量子力学。

image.png

好了,回到程序里面。

虽然注释里面说了返回值是 estimate 的,但是在我们的程序中,并不存在这样的问题。

看到 activeCount 方法的实现之后:

public static int activeCount() {
    return currentThread().getThreadGroup().activeCount();
}

我又想到,既然在直接 Run 的情况下,程序返回的数是 2,那我看看到底有那些线程呢?

其实最开始我想着去 Debug 一下的,但是 Debug 的情况下,返回的数是 1。我意识到,这个问题肯定和 idea 有关,而且必须得用日志调试大法才能知道原因。

于是,我把程序改成了这样:

image.png


直接 Run 起来,可以看到,确实有两个线程。

一个是 main 线程,我们熟悉。

一个是 Monitor Ctrl-Break 线程,我不认识。

但是当我用 Debug 的方式运行的时候,有意思的事情就发生了:

image.png

目录
相关文章
|
Dubbo Java 应用服务中间件
项目中引进这玩意,排查日志又快又准
随着微服务盛行,很多公司都把系统按照业务边界拆成了很多微服务,在排错查日志的时候,因为业务链路贯穿着很多微服务节点,导致定位某个请求的日志以及上下游业务的日志会变得有些困难。
|
5月前
|
SQL 关系型数据库 MySQL
(十八)MySQL排查篇:该如何定位并解决线上突发的Bug与疑难杂症?
前面《MySQL优化篇》、《SQL优化篇》两章中,聊到了关于数据库性能优化的话题,而本文则再来聊一聊关于MySQL线上排查方面的话题。线上排查、性能优化等内容是面试过程中的“常客”,而对于线上遇到的“疑难杂症”,需要通过理性的思维去分析问题、排查问题、定位问题,最后再着手解决问题,同时,如果解决掉所遇到的问题或瓶颈后,也可以在能力范围之内尝试最优解以及适当考虑拓展性。
278 3
|
7月前
|
XML SQL 前端开发
Bug积累
Bug积累
51 1
|
8月前
|
网络协议 容灾 NoSQL
阿里云DTS踩坑经验分享系列|网络问题排查大法
在DTS的所有用户问题中,网络问题出现的概率居高不下,很大程度上是由于DTS的链路复杂性,从源数据库到DTS再从DTS到目的数据库,任意的一个部位发生网络不通、网络质量问题都有可能导致DTS任务的中断,或者延迟。本文希望以一种最简单的模型,简述DTS网络不通问题的排查方法,并给出一些简单的验证思路及手段,排查方向对了才能事半功倍。
109151 3
阿里云DTS踩坑经验分享系列|网络问题排查大法
|
运维 监控 前端开发
记一次线上 bug 的排查分析过程及总结
记一次线上 bug 的排查分析过程及总结
记一次线上 bug 的排查分析过程及总结
|
Java API 容器
java项目排查bug
java项目排查bug
138 0
|
Arthas NoSQL Java
线上服务器CPU100%的真相排查【Bug利器Arthas】
这起CPU100%的事故,由某个客户演示的bug暴露出来,气氛比较尴尬....
772 0
线上服务器CPU100%的真相排查【Bug利器Arthas】
|
消息中间件 监控 算法
JVM技术之旅-线上分析排查问题
JVM技术之旅-线上分析排查问题
307 0
JVM技术之旅-线上分析排查问题
|
测试技术
如何处理不能复现的bug?软件测试工程师避坑指南
软件测试工作中常常会遇到不能复现的bug,遇到这种情况其实很正常,但是很多测试新手都按照自己的想法处理,没有提交bug,或者匆匆关闭bug。线上出现问题,就只能自己背锅了。
573 0
|
Web App开发 移动开发 前端开发
技术分享 | Bug定位方法
技术分享 | Bug定位方法