线程池 操作不规范导致的死锁问题
2022/3/31 23:22:02
本文主要是介绍线程池 操作不规范导致的死锁问题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
起因
利润校验地方,我封装了底层的利润校验,查询京东价格用了自定义线程池A批量去查询,然后别的同事也需要用到我的利润校验,他也使用了线程池A去处理逻辑(去进行利润校验,但是我的利润校验也是用的线程池A),这就导致,上层的线程池A去添加任务,上层的线程池由于任务比较多,或者多次执行,导致线程池的核心线程一直在执行,然后下层任务放到了队列中,一直等待核心线程执行完毕,但是核心线程又在等待下层利润校验的返回值。然而上层的线程池A做的任务是调用下层的线程池A,即上层使用了线程池A调用下层的利润校验,利润校验也是用的线程池A。结果就导致,上层的逻辑需要等待下层逻辑的返回,但是下层的逻辑一直得到不到执行(需要等待上层释放资源,让出空间,下层才有机会执行),就造成了彼此相互等待。
查因
流量峰值时发现大量调用超时,通过链路追踪锁定超时发生的节点,隔离节点后,在Pod中使用jstack命令追踪进程
jstack -l 1 |grep "java.lang.Thread.State"|sort -nr|uniq -c
发现有大量线程阻塞在WAITING状态
11 java.lang.Thread.State: WAITING (parking) 2 java.lang.Thread.State: WAITING (on object monitor) 8 java.lang.Thread.State: RUNNABLE
Dump线程信息:jstack -l 1 > stack.log,分析WAITING状态的线程 ,发现 线程池出现了嵌套提交 造成死锁
分析 举例
核心代码如下,外层任务
public void test() throws ExecutionException, InterruptedException { List<Future<String>> outerFutureList = new ArrayList<>(10); for (int i = 0; i < 11; i++) { outerFutureList.add( executorService.submit( new BadTask(executorService) ) ); } // 阻塞等待完成 for (Future<String> outerFuture : outerFutureList) { System.out.println(outerFuture.get()); } executorService.shutdownNow(); }
BadTask类中造成问题的 主要代码
private static class BadTask implements Callable<String>{ private BadTask(ExecutorService executorService) { this.executorService = executorService; } @Override public String call() throws InterruptedException { // rpc等耗时操作 TimeUnit.MILLISECONDS.sleep(10L); List<Future<String>> innerFutureList = new ArrayList<>(5); // 因为有些SDK的接口有参数数量限制,所以多次调用,为提高RT,复用线程池并发调用 for (int i = 0; i < 5; i++) { innerFutureList.add( // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓这里又提交了一些Callable到同一个线程池 this.executorService.submit( ()->{ // rpc等耗时操作 TimeUnit.MILLISECONDS.sleep(20L); return "OK"; } ) ); } // 阻塞等待全部完成 for (Future<String> innerFuture : innerFutureList) { innerFuture.get(); } return "OK"; } }
结论
完全死锁的必要条件
- 线程池必须有阻塞队列,亦即有可能部分Runnable对象存在阻塞队列,实际上发生死锁的就是Worker里执行的Runnable和阻塞队列的Runnable
- 父任务和子任务向同一个线程池提交任务【核心原因】
- 父任务阻塞等待所有子任务完成,子任务部分在阻塞队列里
- Worker里全为父任务,且对应的所有子任务都在阻塞队列里
破坏以上任何条件,都不会造成完全死锁,但是只要符合条件1,2,在某些情况下仍旧会造成线程执行慢
解决方法
- 不使用阻塞队列,即使用同步队列
java.util.concurrent.SynchronousQueue
- 父任务和子任务使用不同的线程池
- 控制并发低于共享线程池的核心线程数(仅在无法使用上述方案时使用)
这篇关于线程池 操作不规范导致的死锁问题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-01成为百万架构师的第一课:设计模式:Spring中的设计模式
- 2025-01-01一个基于注解驱动的可视化的DDD架构-超越COLA的设计
- 2025-01-01PlantUML 时序图 基本例子
- 2025-01-01plantuml 信号时序图
- 2025-01-01聊聊springboot项目如何优雅进行数据校验
- 2024-12-31自由职业者效率提升指南:3个时间管理技巧搞定多个项目
- 2024-12-31适用于咨询行业的项目管理工具:提升跨团队协作和工作效率的最佳选择
- 2024-12-31高效协作的未来:2024年实时文档工具深度解析
- 2024-12-31商务谈判者的利器!哪 6 款办公软件能提升春节合作成功率?
- 2024-12-31小团队如何选择最实用的项目管理工具?高效协作与任务追踪指南