ThreadPoolExecutor 不终止但 shutdownNow() returns 一个空列表

ThreadPoolExecutor does not terminate but shutdownNow() returns an empty list

在我的一些 jUnit 测试中,我创建了一个 ThreadPoolExecutor 来测试我的代码是否没有明显的并发错误。在每次此类测试结束时,我关闭执行程序并验证是否所有任务都已完成,类似于以下代码:

// wait on a conditional that indicates that results are available
executor.shutdown();
executor.awaitTermination(100l);
// do other result verifications here
if ( ! executor.isTerminated()) {
    final var stuckTasks = executor.shutdownNow();
    for (var stuckTask: stuckTasks) log.severe("stuck " + stuckTask);
    fail("executor not terminated, " + stuckTasks.size() + " tasks remaining");
}

如果我 运行 在一个循环中进行这些测试,每隔几个小时我就会收到一条失败消息“executor not terminated, 0 tasks remaining

这是正常的并且可以安全地忽略还是意味着我的代码实际上有一些并发错误?
除了未完成的任务,还有其他可能导致执行器不终止的原因吗?

我想强调的是,从未发生剩余任务数非零的情况(即使在 运行循环测试之后超过 12 小时,其中单个 运行 大约需要 2s) 并且所有其他验证都正确通过,如果提交给此执行程序的任何任务实际上卡住了,这将是不可能的。
在这种情况下,我等待终止的 100 毫秒很多,因为所有提交的任务都需要不到 10 毫秒 它们都应该在 awaitTermination(100l) 被称为结果之前完成已经有货了。

我在 ubuntu 上使用 openJdk-11 以防万一。

有问题的代码是 here 如果有人感兴趣(我已经将其修改为仅当剩余任务数不为零时才会失败,到目前为止它似乎无害)。在这个 class.

中的各种随机测试方法中,失败仅发生在 userExecutor 上(从未发生在 grpcExecutor 上,这使得它更奇怪)

谢谢!

更新:

正如@Thomas 在评论中指出的那样,shutdownNow() returns 只有甚至没有开始的任务。除此之外,还应该检查getActiveCount()

总结评论:

ThreadPoolExecutor 维护一个 not yet 的队列,shutdownNow() 清空该队列和 returns 它的内容。这意味着您只会获得 尚未开始 .

的任务

这与 Javadoc 一致:

Returns: list of tasks that never commenced execution.

此外,ThreadPoolExecutor(实际线程池)中有一组worker,可以使用ThreadPoolExecutor.getActiveCount()查询活动任务数。该方法基本上查询每个 workder 以查看它当前是否有一个表明它正在执行任务的锁。

如需更完整的图片,请查看 ThreadPoolExecutor.getTaskCount()。它将几个不同的数字相加:

  • 向执行者报告的已完成任务数(被移除的工人)
  • 每个现有工作人员完成的任务数
  • 每个当前活跃的工作人员+1
  • 工作队列的大小(由shutdownNow()返回。

如果我们能够访问工作人员当前正在执行的内容,那就太好了,但我没有找到方法,因为 workers(工作人员的集合)没有暴露给外界并且是私有的,工作人员似乎甚至没有引用他们正在执行的任务(至少不是直接引用)。

现在 shutdown() 尝试打断空闲的工作人员,而不是活动的工作人员,因此 awaitTermination() 可能会因工作人员仍在活动而超时。另一方面 shutdownNow() 中断 all workers.