线程池敢这样用?差点被开除......

本文通过一个数据迁移job在测试中出现的线程Hang住问题,深入分析了线程池工作原理和死锁的四个必要条件。问题源于主任务和子任务共用线程池导致的死锁,表现为大量线程处于WAITING状态。解决方案包括使用非阻塞队列或为父任务和子任务设置独立线程池,以避免类似生产环境中可能引发的接口响应超时。提醒开发者在编码时要对每一行代码负责,避免此类低级错误。
摘要由CSDN通过智能技术生成

现象

有一个数据迁移的 job,在本地测试的过程中直接 Hang 住了,半天没有反应。

通过 jstack 分析线程状态,发现大量处于 WAITING 状态的业务线程。

看了对应的代码后,也迅速把问题定位到了,下面主要分析问题主要发生的原因。

另外如果这个案例发生在生产环境并发较高的 ToC 接口,很可能造成大量接口响应超时,后果不堪设想(保不准真被开除了)。

线程池回顾

线程池的作用:统一管理线程资源,任务执行过程,达到线程资源复用的目的。

线程池处理任务过程:

  • 判断当前线程数是否达到核心线程的阈值,如果没有,则创建线程执行当前任务
  • 如果当前线程数达到核心线程阈值,则会尝试把当前任务放进任务队列,等待空闲线程去处理
  • 如果任务队列也满了,则会尝试创建非核心线程执行当前任务
  • 如果当前线程数达到最大线程数的阈值,则会触发拒绝策略

问题分析

核心原因:主任务和子任务使用同一线程池执行,最终造成线程池死锁,属于线程池使用不当造成的问题。

这个问题看似比较低级,但一不留心也非常容易犯,在部门 code review 的过程中就发现有人这么使用。

业务场景

在做商户门店数据迁移的时候,涉及的数据量比较大,因此将任务进行了如下拆分:

  • 将全量门店拆分成一个个批次,每个批次包含 100 个门店,一个批次的处理定义为一个主任务
  • 一个批次中再以单个门店为单位,拆分成一个个子任务
死锁问题

死锁出现的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用
  • 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

下面主要分析线程死锁出现的原因,问题发生的前提条件是:主任务一次性把核心线程都打满了,导致子任务无可用线程,只能先进入任务队列等待处理。

  • 一个线程资源同一时间只能执行一个任务(即被一个任务使用),满足互斥条件
  • 线程因执行任务被阻塞时,无法释放当前线程资源,满足占有且等待条件
  • 当前任务未执行完前,线程无法被其它任务抢占,满足不可强行占有条件
  • 子任务因为没有线程资源,导致一直待在任务队列中无法被执行;而主任务又因为子任务没有执行完而进入阻塞,无法释放持有的线程资源,满足循环等待条件

示例代码

核心代码如下(大家引以为鉴),外层任务

任务A中造成问题的代码

解决方案
  • 线程池不使用阻塞队列,使用同步队列(这样可能会造成任务串行执行,达不到并发的效果)
  • 父任务和子任务使用不同的线程池(采用的是此种解决方案)
  • 控制父任务并发数低于核心线程数

通过这个事件分析,也警醒我们平时写代码的过程中要多思考每一行代码的含义,而不是简单地CV,为自己写下的每一行代码负责。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值