Spring的ThreadPoolTaskExecutor的池大小问题

我正在对我的Spring应用程序进行一些负载测试,现在我对.ThreadPoolTaskExecutor

内部使用的文档将“要保留在池中的线程数,即使它们处于空闲状态,[...]”和“池中允许的最大线程数”来描述。ThreadPoolExecutorcorePoolSizemaximumPoolSize

这显然意味着限制了池中的线程数。但相反,限制似乎是由 .实际上我只配置了一个 let unconfiged(这意味着使用默认值:= )。maximumPoolSizecorePoolSizecorePoolSize100maximumPoolSizeInteger.MAX_VALUE2147483647

当我运行负载测试时,我可以看到(通过查看日志),执行的工作线程的编号为从 到 。因此,在本例中,线程池大小受 限制。即使我设置为 或 ,结果也是完全相同的。worker-1worker-100corePoolSizemaximumPoolSize200300

为什么 对我的情况,为什么 的价值没有影响?maximumPoolSize

@Bean
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(100);
    taskExecutor.setThreadNamePrefix("worker-");
    return taskExecutor;
}

溶液

我在文档中找到解决方案:“如果运行的线程数超过corePoolSize但小于maxmousPoolSize线程,则仅当队列已满时才会创建新线程”。缺省队列大小为 。如果我限制队列,一切正常。Integer.MAX_VALUE


答案 1

我已经做了一些测试,有三件事你必须明白:ThreadPoolTaskExecutor

  • 核心池大小
  • 队列容量
  • 最大池大小

启动进程时,池中没有线程。每当任务出现时,只要没有达到,就会创建一个新的执行器线程来处理这个新负载。当到达 时,下一个任务将被转移到队列并等待一个空闲的执行器线程。如果负载过高且已满,则将创建新的执行器线程,除非达到 。这些附加线程将在队列为空时立即过期。如果 已用尽,已满且也已达到,则新提交的任务将被拒绝,并且调用将收到异常。corePoolSizecorePoolSizequeueCapacitymaxPoolSizecorePoolSizequeueCapacitymaxPoolSize

您没有提到您的配置,因此它可能被设置为最高整数,因此永远不会被触发。尝试使用小,您将观察到所需的结果。queueCapacitymaxPoolSizecorePoolSizequeueCapacity


答案 2

如果池中有 100 个线程,并且正在 4 个物理 CPU 内核上执行 CPU 绑定代码,则池中的大多数核心线程都处于空闲状态,等待重用。这可能就是为什么你看到的不仅仅是工人100。

您没有向我们显示您正在workers中执行的代码,因此我假设它不受I / O限制。如果它是 I/O 绑定代码,并且 100 个核心线程将被等待阻塞 I/O 操作完成而占用,则需要创建其他工作线程。ThreadPoolExecutor

尝试使用低于计算机上的内核数进行确认。另一种选择是输入您的工人代码,并观察您的工人数量将如何提高。corePoolSizeThread.sleep(1000)

编辑:

您建议在评论中使用。注意Spring框架文档的这一部分:SimpleAsyncTaskExecutor

SimpleAsyncTaskExecutor 此实现不重用任何线程,而是为每个调用启动一个新线程。但是,它确实支持并发限制,该限制将阻止任何超出限制的调用,直到释放插槽为止。如果您正在寻找真正的池化,请参阅下面 SimpleThreadPoolTaskExecutor 和 ThreadPoolTaskExecutor 的讨论。

因此,您根本没有池化,并且大量资源(包括CPU周期)浪费在创建和删除对象上,这可能是非常昂贵的操作。SimpleAsyncTaskExecutorThread

因此,执行器类型对负载测试弊大于利。如果你想有更多的工人,使用更多的机器。如果要进行准确的负载测试,则仅使用一台计算机是天真的。SimpleAsyncTaskExecutor