线程池执行器使用无界队列时未创建新线程

21

我的ThreadPoolExecutor无法创建新线程。事实上,我编写了一个有点“hacky”的LinkedBlockingQueue,它将接受任何任务(即它是无界的),但会调用额外的处理程序 - 在我的应用程序中,它会输出警告追踪,表示池已滞后 - 这为我提供了非常明确的信息,即TPE拒绝创建新线程,即使队列中有成千上万的条目。我的构造函数如下:

private final ExecutorService s3UploadPool = 
new ThreadPoolExecutor(1, 40, 1, TimeUnit.HOURS, unboundedLoggingQueue);

为什么它没有创建新线程?


与https://dev59.com/xWIk5IYBdhLWcg3wKLSd#19528305相关的内容。 - Gray
3个回答

26

这个问题在这篇博客文章中得到了解答:

线程池的构造方式不能如预期那样工作,这是由于ThreadPoolExecutor内部逻辑会在任务无法提交到队列时新建线程。在我们的情况下,我们使用一个无界的LinkedBlockingQueue,即使我们总是可以将任务提交到队列中。这实际上意味着我们永远不会超过核心池大小和最大池大小之间的范围。

如果您还需要将最小池大小与最大池大小解耦,那么您需要进行一些扩展编码。我不知道有没有Java库或Apache Commons中存在的解决方案。解决方案是创建一个与TPE相关的耦合BlockingQueue,如果它知道TPE没有可用的线程,则会拒绝任务并手动重新排队。更多细节可以在链接的文章中找到。最终,您的构造方式将如下所示:

public static ExecutorService newScalingThreadPool(int min, int max, long keepAliveTime) {
   ScalingQueue queue = new ScalingQueue();
   ThreadPoolExecutor executor =
      new ScalingThreadPoolExecutor(min, max, keepAliveTime, TimeUnit.MILLISECONDS, queue);
   executor.setRejectedExecutionHandler(new ForceQueuePolicy());
   queue.setThreadPoolExecutor(executor);
   return executor;
}

然而更简单的方式是将corePoolSize设置为maxPoolSize,不必担心这些无聊的事情。


请注意,您仍然可以通过允许核心线程超时来获得有限的扩展效果(您可以从0个线程扩展到最大线程数)。 - jtahlborn
根据Javadocs的说明:通过将corePoolSize和maximumPoolSize设置为相同,您可以创建一个固定大小的线程池。因此,如果您遵循您最后一句话,最好只使用Executors.newFixedThreadPool(poolSize)。 - darrickc
@darrickc 看起来你的编辑已经被拒绝了,但在评论中包含它是合适的。我认为它无效,因为在我的用例中,我不希望线程超时。 - djechlin
任务被拒绝是什么意思?有什么后果?另外,当我将Android的AsyncTask代码更改为使用简单的LinkedBlockingQueue时,为什么它可以按照我设置ThreadPoolExecutor CTOR的参数正常工作并创建新线程? - android developer

6
这个问题有一个解决办法。考虑以下实现:
int corePoolSize = 40;
int maximumPoolSize = 40;
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 
    60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
threadPoolExecutor.allowCoreThreadTimeOut(true);

通过将 allowCoreThreadTimeOut()设置为true,则允许池中的线程在指定时间(此示例中为60秒)后终止。使用此解决方案时,实际上是 corePoolSize 构造函数参数确定了最大池大小,因为线程池将增长到 corePoolSize ,然后开始向队列添加作业。由于该池不会生成新线程直到队列已满(由于 LinkedBlockingQueue 具有 Integer.MAX_VALUE 容量可能永远不会发生),因此很可能池永远不会变得比那更大。因此,将 maximumPoolSize 设置为大于 corePoolSize 的值没有什么意义。

注意:在超时到期后,线程池将拥有0个空闲线程,这意味着在创建线程之前会存在一些延迟(通常情况下,您始终会有可用的 corePoolSize 个线程)。

有关详细信息,请参见 ThreadPoolExecutor 的JavaDoc。


5
如@djechlin所说,这是ThreadPoolExecutor(线程池执行器)的定义行为(对许多人来说可能很惊奇)。我相信我在这里展示了一个比较优雅的解决方案:如何让ThreadPoolExecutor在排队之前增加线程到最大?基本上,您可以扩展LinkedBlockingQueue,使其始终返回false以queue.offer(...),这将在必要时向线程池添加其他线程。如果池已达到最大线程数且它们都很忙,则将调用RejectedExecutionHandler。然后由处理程序调用put(...)进入队列。请参见我的代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接