【并发问题】线程池死锁阻塞

博客讲述了作者在工作中遇到的一个线上问题,即由于同一线程池中父任务和子任务的嵌套执行导致的504错误。问题的根本原因是线程池资源被父任务耗尽,子任务无法执行,形成死锁。通过分析日志和代码,发现是使用`FutureTask`时的阻塞操作引发了问题。解决方案是避免线程池任务的嵌套,可以使用不同的线程池执行父任务和子任务。博客强调了扎实的基本功对于避免这类问题的重要性。
摘要由CSDN通过智能技术生成

苦练基本功

问题表现

周五开开心心下班,结果收到了问题,说是某个接口报了504的异常,一开始以为是网络或者服务器的问题,后来排查才发现是自己埋的一个坑,快乐的周末直接报销。

排查过程

首先关注一下504代表的问题。

造成504的原因主要有两种,nginx建立连接超时(proxy_connect_timeout)和nginx等待服务器返回超时(proxy_read_timeout)

常见的原因可能是流量在增加,但是后端容量不足,导致大量504出现。

但是在这里很明显不是资源问题,应该是nginx等待服务器返回超时,而请求的执行时间按理说不应该有这么长,所以很有可能是代码在某个地方阻塞了。

之后登录机器排查日志,跟着日志执行请求发现请求后半段的日志都消失了,就好像是卡在一半不动了一样。

于是再看实现,发现有把请求放到线程池里执行的过程,于是去监控平台查看线程池里线程的阻塞状态,发现全部都阻塞在java.util.concurrent.FutureTask,于是意识到了问题所在。

问题根因

先讲具体的实现逻辑,这里的请求是一个excel批量导出的任务,但是因为数据量大,而且数据有分层结构,所以单个导出任务分为了两层执行,第一层取汇总信息,第二层取明细数据;由于并发度的问题,两层都需要用线程池来并发进行,所以实现时用了同一个线程池来执行这两层的任务。

而这里线程池任务的嵌套结构,就是出问题的原因

这里是因为优先把所有父任务提交到了线程池中,并且使用future.get使其进入了阻塞状态,当父任务的数量大于可用线程数时,所有正在执行的线程都在等待等待队列阻塞线程的结果,线程资源会在执行完成后再释放,而子任务提交到线程池中后没有资源执行,父任务也就一直在阻塞状态,无法执行完成释放资源,最终导致死锁。

写个demo模拟一下当时的场景:

        ExecutorService pool = Executors.newSingleThreadExecutor();
        pool.submit(() -> {
            try {
                System.out.println("First");
                pool.submit(() -> System.out.println("Second")).get();
                System.out.println("Third");
            } catch (InterruptedException | ExecutionException e) {
                System.out.println(e);
            }
        });

确实会在输出First之后一直阻塞。

解决方法

那么应该如何解决呢,先复习一下死锁的概念。死锁产生的四个条件:互斥条件,保持和请求,不可剥夺,循环等待,四大条件,缺一不可。

而解除死锁的方式也很简单,只需要破坏其中的某个条件就行了:

1、增加超时时间限制,但是这只是解决死锁而不是解决问题,达到条件后还是会触发

2、换用两个不同的线程池分别来执行父任务和子任务

问题总结

问题说大不大,说小不小,虽然相当于把线上搞挂了,但是因为线程池使用范围有限,所以影响比较小。

不过用线程池的时候完全没有想到会有这个问题,只能说是基本功还是不够扎实呀,这下真得苦练基本功了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值