# 记一次 Java Springboot 线程池问题 (误)
# 设备
- arm64 macOS
# 环境
- Java corretto-17
- Springboot 3.0.2
# 遇到的问题
- 问题描述
当使用Springboot自带注入的ThreadPoolTaskExecutor线程池时,遇到比较诡异的线程阻塞问题。调用代码:
// 触发创建操作 | |
var finalGroup = group; | |
var path = getGroupParentPath(finalGroup.getId()); | |
var sourcePath = finalGroup.getSourceId()+"/"+path; | |
var event = new ChokidarEvent().setEvent(ChangeType.addDir).setPath( | |
sourcePath | |
); | |
threadPoolTaskExecutor.submit(() -> { | |
sendChokidarEvent(finalGroup.getSourceId(), event); | |
}); |
- 问题复现
代码在执行sendChokidarEvent时被阻塞,如果用submit返回的Future的get方法等待线程结束时,可以发现整个方法被阻塞住。
线程的执行时机,当在另外一个函数执行删除操作发送同样的事件代码时,创建操作sendChokidarEvent被连带执行,同时执行顺序删除操作在创建操作之前。
删除代码为:
threadPoolTaskExecutor.submit(new Thread(()->{ | |
if (subscribers.containsKey(finalGroup.getSourceId())) { | |
var path = getGroupParentPath(finalGroup.getParent()); | |
var sourcePath = finalGroups.get(0).getSourceId() + "/" + path + "/" + id; | |
if (!StringUtils.hasText(path)){ | |
sourcePath = finalGroups.get(0).getSourceId() + "/" + id; | |
} | |
sendChokidarEvent(finalGroup.getSourceId(), new ChokidarEvent().setEvent(ChangeType.unlinkDir).setPath(sourcePath)); | |
} | |
})); |
- 测试结果
此时使用 POSTMAN 测试 SseEmitter 的输出情况,可以发现unlinkDir事件在addDir之前被传送。
# 已知
ThreadPoolTaskExecutor 是阻塞队列写法,由于某种条件导致 addDir 函数 sendChokidarEvent 被一直阻塞。
# 解决方法
- 现有线上资料并无类似的问题。
- 查看源码
在查看了源码之后,可以发现 ThreadPoolTaskExecutor 只是 ThreadPoolExecutor 的一个包装器,更进一步通过查看 ThreadPoolExecutor 可以发现其中间并没有主动阻塞运行的代码。而在又一次的断点之后发现 getGroupParentPath 才是后续代码片段断点消失的原因。
# 问题解决
本次问题是一个低级错误,由于 getGroupParentPath 中变量写错导致的死循环造成后续的代码不执行。