# 记一次 Java Springboot 线程池问题 (误)

# 设备

  1. 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 返回的 Futureget 方法等待线程结束时,可以发现整个方法被阻塞住。
    线程的执行时机,当在另外一个函数执行删除操作发送同样的事件代码时,创建操作 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 被一直阻塞。

# 解决方法

  1. 现有线上资料并无类似的问题。
  2. 查看源码

在查看了源码之后,可以发现 ThreadPoolTaskExecutor 只是 ThreadPoolExecutor 的一个包装器,更进一步通过查看 ThreadPoolExecutor 可以发现其中间并没有主动阻塞运行的代码。而在又一次的断点之后发现 getGroupParentPath 才是后续代码片段断点消失的原因。

# 问题解决

本次问题是一个低级错误,由于 getGroupParentPath 中变量写错导致的死循环造成后续的代码不执行。

更新于 阅读次数