# 记一次 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
中变量写错导致的死循环造成后续的代码不执行。