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

苦练基本功

问题表现

周五开开心心下班,结果收到了问题,说是某个接口报了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、换用两个不同的线程池分别来执行父任务和子任务

问题总结

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

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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值