线程池嵌套导致的死锁问题

devtools/2024/9/22 22:10:15/

1、背景

有一个报告功能,报告需要生成1个word,6个excel附件,总共7个文件,需要记录报告生成进度,进度字段jd初始化是0,每个文件生成成功进度加1,生成失败就把生成状态置为失败。

更新进度语句:update bg set jd = jd+1 where id = 'xx' 

上线一段时间后,很多报告进度都没有100%

2、问题排查

查看线上日志,发现生成附件2、附件3有时候会报错,然后对着报错改了代码,还是觉得有问题。因为看了代码,7个文件生成用的7个线程,每个结构都是try,catch,finnally,

看下面代码,感觉每个子线程都是走到finally里面,那就要么更新进度为100%,要么更新状态为失败

public void creatBgExcel(CreateBgWjPo createBgWjPo) {
..............................
        //附件1
        CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {
            //新增数据excel,附件1
            createXzsjExcel(createBgWjPo, tempFileDir);
        }, threadPoolExecutor);

        //附件2
        CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {
            //缺失数据excel,附件2
            createWtsjExcel(createBgWjPo, tempFileDir);
        }, threadPoolExecutor);

     ...............................
        CompletableFuture<Void> headerFuture = CompletableFuture.allOf(task1,task2 ,task3,.......);
        headerFuture.join();
        log.info("--bgId:{},excel报告单个附件已经全部生成", bgId);
    }

public void createWtsjExcel(CreateBgWjPo createBgWjPo, String tempFilePath) {log.info("--附件2,问题数据excel,开始生成数据,bgmc:{}", createBgWjPo.getGxZlbg().getBgmc());String exelxx = "";boolean isSucess = false;String msg = "";try{dosomething();isSucess = true;}catch (Exception e) {log.error("createWtsjExcel附件2生成失败,bgmc:{}",  createBgWjPo.getGxZlbg().getBgmc(),e);msg = "附件2生成excel失败.失败原因:{}" + e.getMessage();} finally {if (isSucess) {//更新进度gxZlbgMapper.updateBgJd(createBgWjPo.getGxZlbg().getId());} else {//更新状态bgZtToFail(createBgWjPo.getGxZlbg().getId(), msg, false, true);}}
}
(-)怀疑1,难道没有进入finally方法?
finanlly一般不执行的情况:
1、代码存在死循环
try{while(true){
}catch (Exception e) {} finally {
}
排查了代码,没有死循环,显然不适用。按理只要进入了子方法,肯定会进入finally。
排查线上日志docker logs -f --tail 100000 api-xx  grep 有问题的报告名称
发现: 有问题的报告名称,有些子文件没有打印出  开始生成数据的日志
结论: 有些报告生成的时候,根本没有进入对应的子方法
(2)排查方法外层代码ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 10,10, TimeUnit.SECONDS,new LinkedBlockingQueue<>(Integer.MAX_VALUE));@Overridepublic SwaggerResultUtil<String> createZlbg(String bgId,boolean isReCreate) {CompletableFuture.runAsync(() -> {GxZlbg zlbg = new GxZlbg();zlbg.setId(bgId);//更新附件地址zlbg.setFjsczt(BgztEnum.DOING.getCode());zlbg.setWdsczt(BgztEnum.DOING.getCode());gxZlbgMapper.updateById(zlbg);CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {//创建excelcreatBgExcel(result.getData());}, threadPoolExecutor);CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {//创建wordcreateBgWord(result.getData());}, threadPoolExecutor);CompletableFuture<Void> headerFuture = CompletableFuture.allOf(task1, task2);headerFuture.join();}, threadPoolExecutor);return SwaggerResultUtil.resultSuccess();}

看到这段代码,发现存在线程池套线程池。

线程池8个,核心线程10个,问题分析

任务

外部线程

执行任务内部线程

备注

任务136里面子任务被执完成,外层的线程才会被释放
任务236
任务336

这样如果有3个报告同时生成,而且子文件方法耗时长,就会出现线程都被外部线程占用,内部无线程可用的状况,出现死锁,后续报告都无法开始生成。跟线上问题完全符合,应该就是发生了线程死锁

3、问题解决

(1)不使用线程池套线程池的办法,把最外层方法指定线程池去掉threadPoolExecutor。

存在问题,如果里面线程池最大线程数10,执行里层所有方法需要线程数据>10, 外部没有线程池,最大可能10个任务同时并发,10个任务互相等待子线程,虽然没有死锁,但是每个任务都很慢,而且可能引起爆内存。这个方案只适用很少的场景

(2)外层一个线程池,里面一个线程池。 这样可以控制外层是按顺序执行,以及控制外层的并发数

(3)所有任务,先写入redis队列,然后定时任务巡检redis队列数据,取出任务一个个执行。 redis+定时任务+线程池方案, 这个方案,很容易排查哪里容易没有执行

上面方案按具体需求选取

 


http://www.ppmy.cn/devtools/19881.html

相关文章

《Beginning C++20 From Novice to Professional》第四章 Making Decisions

It means altering the sequence of execution depending on the result of a comparison 书上对做决策解释得挺清楚的&#xff0c;指的是根据比较的结果更改执行顺序的这个过程&#xff1b;这也是将计算机与计算器区别开的一个重要特征&#xff0c;计算机能够做决策 不过根本…

DNS域名解析服务

BIND域名服务基础 DNS系统的作用及类型 BIND的安装和配置文件 使用BAND构建域名服务器 构建缓存域名服务器 构建主和从域名服务器 DNS系统的作用 正向解析&#xff1a;根据域名查找对应的IP地址 反向解析&#xff1a;根据IP地址查找对应的域名 DNS系统分布式数据结构 DNS…

宜搜科技死磕港交所上市:从搜索引擎到广告投放,业绩疲态凸显

近日&#xff0c;宜搜科技控股有限公司&#xff08;下称“宜搜科技”&#xff09;向港交所递交招股书&#xff0c;计划在香港主板上市&#xff0c;中银国际为其独家保荐人。 值得注意的是&#xff0c;宜搜科技已在资本市场辗转多年。该公司曾于2014年向纽交所递交上市申请&…

【区块链】椭圆曲线数字签名算法(ECDSA)

本文主要参考&#xff1a; 一文读懂ECDSA算法如何保护数据 椭圆曲线数字签名算法 1. ECDSA算法简介 ECDSA 是 Elliptic Curve Digital Signature Algorithm 的简称&#xff0c;主要用于对数据&#xff08;比如一个文件&#xff09;创建数字签名&#xff0c;以便于你在不破坏它…

int和byte数组相互转换详解

转换之前我们需要了解各种进制之间的关系&#xff0c;不太了解的可以先看下计算机组成原理和体系 这篇文章 byte byte是字节的意思&#xff0c;一个字节等于8位&#xff0c;范围是 0000 0000 ~ 1111 1111(十六进制&#xff1a;0x0~0xff),总共包含256个数。 有符号的byte表示的…

prompt炼金:ChatGPT在文献综述中100+类高阶提示词应用

点击下方▼▼▼▼链接直达AIPaperPass &#xff01; AIPaperPass - AI论文写作指导平台 近期小编沉迷总结ChatGPT提示词&#xff0c;从之前涵盖全流程的数百条提示词到今天一步一步精炼每个流程中宝子们可能用的上的提示词。今天分享给大家文献综述相关提示词技巧。 如何提升你…

十一、多模态大语言模型(LLaVA)

1 LLaVA多模态大语言模型的训练过程 两个阶段 特征对齐的预训练。只更新特征映射矩阵端到端微调。特征投影矩阵和LLM都进行更新 2 LLaVA1.5多模态大语言模型的训练 LLaVA官网 python -m llava.serve.controller --host 0.0.0.0 --port 10000 python -m llava.serve.gradi…

goroutinue和channel

goroutinue和channel 需求传统方式实现goroutinue进程和线程说明并发和并行go协程和go主线程MPG设置Go运行的cpu数 channel(管道)-看个需求使用互斥锁、写锁channel 实现 使用select可以解决从管道取数据的阻塞问题&#xff08;无需手动关闭channel了&#xff09;goroutinue中使…