上面这个 Demo 已经是我基于遇到的生产问题,极力简化后的版本了。现在,这个坑也已经呈现在你眼前了。
我们一起来分析一波。首先我问你真的在线上遇到这种程序“假死”的问题你会怎么办早几年歪师傅的习惯是抱着代码慢慢啃试图从代码中找到端倪。这样确实是可以但是通常来说效率不高。现在我的习惯是直接把现场 dump 下来分析现场。比如在这个场景下我们直观上的感受是“卡住了”那就 dump 一把线程管它有枣没枣打一杆子再说通过 Dump 文件可以发现线程池的线程都在 MainTest 的第 30 行上 parking 处于等待状态那么第 30 行是啥玩意这行代码在干啥countDownLatchSub.await();是父任务在等待子任务执行结束运行 finally 代码把 countDownLatchSub 的计数 countDown 到 0才会继续执行所以现在的现象就是子任务的 countDownLatchSub 把父任务的拦住了。换句话说就是父任务被拦住是因为子任务的 finally 代码中的 countDownLatchSub.countDown() 方法没有被执行。好那么最关键的问题就来了为什么没有执行你先别往下看闭上眼睛在你的小脑瓜子里面推演一下琢磨一下finally 为什么没有执行或者再换个更加接近真实的问题子任务为什么没有执行这个点非常简单可以说一点就破。琢磨明白了这个坑的原理摸摸清楚了。.........琢磨明白了吗你就刷刷往下看没明白我再给你一个信息需要结合线程池的参数和运行原理来分析。什么你说线程池的运行原理你不清楚请你取关好吗你个假粉丝。.........好不管你“恍然大悟”了没有歪师傅给你讲一下。让你知道“一点就破”这四个是怎么回事儿。首先我们把目光聚焦在线程池这里这个线程池核心线程数是 3但是我们要提交 5 个任务到线程池去。父任务哐哐哐就把核心线程数占满了。接下来子任务也要往这个线程池提交任务怎么办当然是进队列等着了。一进队列就完犊子。到这里我觉得你应该能想明白问题了。应该给到我一个恍然大悟的表情并配上“哦哦哦~”这样的内心 OS。你想想父任务这个时候干啥是不是等在 countDownLatchSub.await() 这里。而 countDownLatchSub.await() 什么时候能继续执行是不是要所有子任务都执行 finally 后那么子任务现在在干啥是不是都在线程池里面的队列等着被执行呢那线程池队列里面的任务什么时候才执行是不是等着有空闲线程的时候那现在有没有空闲线程没有所有的线程都去执行父任务去了。那你想想父任务这个时候干啥是不是等在 countDownLatchSub.await() 这里。...父任务在等子任务执行。子任务在等线程池调度。线程池在等父任务释放线程。闭环了相互等待了家人们。这就是坑。现在把坑的原理摸清楚了我在给你说一下真实的线上场景踩到这个坑是怎么样的呢上游发起请求到微服务 A 的接口 1该接口需要调用微服务 B 的接口 2。但是微服务 B 的接口 2需要从微服务 A 接口 3 获取数据。然而在微服务 A 内部全局使用的是同一个自定义线程池。更巧的是接口 1 和接口 3 内部都使用了这个自定义线程池做异步并行处理想着是加快响应速度。整个情况就变成了这样接口 1 收到请求之后把请求转到自定义线程池中然后等接口 2 返回。接口 2 调用接口 3并等待返回。接口 3 里面把请求转到了自定义线程池中被放入了队列。线程池的线程都被接口 1 给占住了没有资源去执行队列里面的接口 3 任务。相互等待一直僵持。我们的 Demo 还是能比较清晰的看到父子任务之间的关系。但是在这个微服务的场景下在无形之间就形成了不易察觉的父子任务关系。所以就踩到了这个坑。