生产故障分析:线程池配置错误导致的阻塞问题

[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Bug-Log-Optimization/thread-pool-use-error.html


在生产环境中,我们遇到了一个由线程池配置错误导致的阻塞问题。本文将对这个问题进行详细分析,并提出相应的解决方案。

问题背景


后端在接收到前端用户请求后,会将请求分成4个线程交给一个线程池处理。这4个线程分别负责请求不同的查询接口,以获取查询结果。最后,将这些结果合并并同步返回给前端用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 提交任务到线程池
waybillNoQueryFuture = submitTask(waybillNoQueryService, waybillNo, currentUserName)
orderInfoQueryFuture = submitTask(orderInfoQueryService, waybillNo, currentUserName)
appointmentQueryFuture = submitTask(appointmentQueryService, waybillNo, currentUserName)
operationWaybillQueryFuture = submitTask(operationWaybillQueryService, waybillNo, currentUserName)

// 获取任务结果
waybillNoDto = waybillNoQueryFuture.getResult()
orderInfoDto = orderInfoQueryFuture.getResult()
appointmentDto = appointmentQueryFuture.getResult()
operationWaybillDto = operationWaybillQueryFuture.getResult()

// 合并结果并返回给前端用户
result = mergeResults(waybillNoDto, orderInfoDto, appointmentDto, operationWaybillDto)
return result

相关线程池基本原理


Java线程池(ThreadPoolExecutor)是一种基于线程的Executor框架,它主要用于管理并行执行的任务。线程池中的线程会被复用,从而降低了线程创建和销毁的开销

线程池的主要组成部分包括:

  1. 核心线程数(corePoolSize):线程池中始终保持的线程数量
  2. 最大线程数(maximumPoolSize):线程池中允许的最大线程数量
  3. 工作队列(BlockingQueue):用于存放待处理任务的队列当线程池中的线程数量达到核心线程数时,新的任务会被放入工作队列中等待执行
  4. 拒绝策略(RejectedExecutionHandler):当线程池中的线程数量达到最大值,并且工作队列已满时,新的任务会触发拒绝策略

线程池队列的作用是在线程池中的线程数达到核心线程数时,将新的任务暂存起来,等待空闲线程来处理
如果工作队列长度设置得过短,当线程池中的线程数达到核心线程数,并且工作队列已满时,新的任务将触发拒绝策略
不同的拒绝策略会有不同的处理方式,例如抛出异常、丢弃任务等

问题分析


线程池配置错误


在这个案例中,线程池的配置出现了错误。线程池的等待队列长度被错误地设置为1:

1
2
3
4
corePoolSize = 10; 
maxPoolSize = 20;
queueCapacity = 1;
createThreadPool(corePoolSize, maxPoolSize, queueCapacity, rejectionPolicy);

由于线程池等待队列长度设置过小,每当前端发来一个请求,同时向线程池提交4个任务时,就会超过队列长度。这导致触发了线程池的拒绝策略。在这个案例中,拒绝策略的设置是什么都不做。

1
2
3
4
rejectionPolicy = (task, executor) -> { 
// 什么都不做,丢弃任务,记录日志
log.error("Task rejected"); };
}

因此,主线程无法获得任务执行结果,从而导致一直阻塞。

解决方案


方案1

为了解决这个问题,可以考虑调整线程池的配置,将等待队列长度设置为一个合理的值,以避免触发拒绝策略

1
2
// 调整队列长度 
queueCapacity = 50; // 或 100

方案2

修改拒绝策略

1
2
3
4
5
6
7
rejectionPolicy = (task, executor) -> {
// 线程池繁忙,记录日志,提交让主线程处理
logger.info("thread pool is busy->{},{}", executor.getPoolSize(), executor.getQueue().size());
if (!executor.isShutdown()) {
task.run();
}
}

总结

这次分享了一个由线程池配置错误导致的阻塞问题,并给出了相应的解决方案。
在实际开发中,我们应当注意合理地配置线程池参数,并加强应用监控,以避免类似问题的发生。
理解线程池的原理和配置参数对于合理地利用线程池资源至关重要。在实际项目中,应该根据业务需求和系统资源来设置合适的线程池参数,以保证系统的稳定运行和良好性能。

生产故障分析:线程池配置错误导致的阻塞问题

https://heyfl.gitee.io/Bug-Log-Optimization/thread-pool-use-error.html

作者

神奇宝贝大师

发布于

2022-05-04

更新于

2022-11-04

许可协议

评论