我正在对Spring应用程序进行一些负载测试,现在我对ThreadPoolTaskExecutor
的配置感到有点困惑。
内部使用的ThreadPoolExecutor
的文档将corePoolSize
描述为“保留在池中的线程数,即使它们处于空闲状态,[...]”和{{1} } as“池中允许的最大线程数”。
这显然意味着maximumPoolSize
限制了池中线程的数量。但相反,限制似乎由maximumPoolSize
设定。实际上我只使用corePoolSize
corePoolSize
配置了100
取消配置(这意味着使用了默认值:maximumPoolSize
= Integer.MAX_VALUE
)。
当我运行负载测试时,我可以看到(通过查看日志),执行的工作线程编号从2147483647
到worker-1
。因此,在这种情况下,线程池大小受worker-100
限制。即使我将corePoolSize
设置为maximumPoolSize
或200
,结果也完全相同。
为什么300
的值对我的情况没有影响?
maximumPoolSize
解
我在文档中找到了解决方案:“如果有多个corePoolSize但运行的是不到maximumPoolSize线程,则只有在队列已满时才会创建新线程”。默认队列大小为@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(100);
taskExecutor.setThreadNamePrefix("worker-");
return taskExecutor;
}
。如果我限制队列,一切正常。
答案 0 :(得分:9)
我已对ThreadPoolTaskExecutor
进行了一些测试,您必须了解三件事:
启动进程时,池中没有线程。
每次任务到来时,只要未达到corePoolSize
,就会创建一个新的执行程序线程来处理此新负载。
到达corePoolSize
时,下一个任务将转移到队列并等待一个免费的执行程序线程。
如果加载过高且queueCapacity
已满,则将创建新的执行程序线程,除非达到maxPoolSize
。一旦队列为空,这些额外的线程将过期。
如果corePoolSize
已用尽,queueCapacity
已满,且maxPoolSize
也已达到,那么新提交的任务将被拒绝并被调用将获得例外。
您尚未提及配置的queueCapacity
,因此可能会将其设置为最高整数,因此永远不会触发maxPoolSize
。尝试使用较小的corePoolSize
和queueCapacity
,您将看到所需的结果。
答案 1 :(得分:4)
如果池中有100个线程,并且您正在4个物理CPU核心上执行CPU绑定代码,则大多数核心线程在池中处于空闲状态,等待重新使用。这可能就是为什么你看到的不仅仅是worker-100。
您没有向我们展示您在工作人员中执行的代码,因此我认为它不受I / O限制。如果它是I / O绑定代码并且等待阻塞I / O操作完成将占用100个核心线程,ThreadPoolExecutor
将需要创建其他工作程序。
尝试使用低于机器核心数的corePoolSize
进行确认。另一种选择是将Thread.sleep(1000)
放入您的工作人员代码中,并观察您的工人数量将如何增加。
编辑:
您建议在评论中使用SimpleAsyncTaskExecutor
。请注意Spring Framework docs的这一部分:
SimpleAsyncTaskExecutor此实现不重用任何 线程,而不是为每次调用启动一个新线程。 但是,它确实支持并发限制,这将限制任何 超出限制的调用,直到插槽被释放。如果 你正在寻找真正的汇集,请参阅讨论 下面是SimpleThreadPoolTaskExecutor和ThreadPoolTaskExecutor。
因此,对于SimpleAsyncTaskExecutor
,您根本没有池,并且在创建和删除Thread
对象时浪费了大量资源(包括CPU周期),这可能是非常昂贵的操作。
因此,SimpleAsyncTaskExecutor
执行程序类型对负载测试的弊大于利。如果您想拥有更多工人,请使用更多机器。如果你想要进行准确的负载测试,那么只使用一台机器是天真的。