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个,问题分析
任务 | 外部线程 | 执行任务内部线程 | 备注 |
---|---|---|---|
任务1 | 3 | 6 | 里面子任务被执完成,外层的线程才会被释放 |
任务2 | 3 | 6 | |
任务3 | 3 | 6 |
这样如果有3个报告同时生成,而且子文件方法耗时长,就会出现线程都被外部线程占用,内部无线程可用的状况,出现死锁,后续报告都无法开始生成。跟线上问题完全符合,应该就是发生了线程死锁
3、问题解决
不使用线程池套线程池的办法,把最外层方法指定线程池去掉threadPoolExecutor