FixedThreadPool与CachedThreadPool:两种弊端中的较小者


93

我有一个程序产生线程(〜5-150)来执行一堆任务。最初,我使用a,FixedThreadPool因为这个类似的问题表明它们更适合寿命更长的任务,并且由于我对多线程的了解非常有限,我认为线程的平均寿命(几分钟)是“ 寿命长 ”的。

但是,我最近添加了产生更多线程的功能,并且这样做使我超出了我设置的线程限制。在这种情况下,最好猜测并增加我可以允许的线程数,或者切换到a CachedThreadPool以便没有浪费的线程?

初步尝试将它们都尝试一下,似乎没有什么区别,所以我倾向于选择CachedThreadPool正义以避免浪费。但是,线程的寿命是否意味着我应该改而选择a FixedThreadPool并只处理未使用的线程?这个问题使得它看起来像那些额外的线程不被浪费,但我希望澄清。

Answers:


108

CachedThreadPool正是您应根据情况使用的,因为长时间运行线程不使用CachedThreadPool。Java文档中有关CachedThreadPools适用于短期任务的评论仅表明它们特别适合此类情况,而不是它们不能或不应用于涉及长时间运行任务的任务。

为了进一步阐述,Executors.newCachedThreadPoolExecutors.newFixedThreadPool都由相同的线程池实现(至少在开放的JDK中)支持,只是具有不同的参数。区别只是它们的线程最小值,最大值,线程终止时间和队列类型。

public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
 }

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                 60L, TimeUnit.SECONDS,
                                 new SynchronousQueue<Runnable>());
}

当您确实想使用固定数量的线程时,FixedThreadPool确实具有其优势,因为从那时起,您可以向执行程序服务提交任意数量的任务,同时知道线程数将保持在指定的级别。如果您明确希望增加线程数,那么这是不合适的选择。

但是,这确实意味着CachedThreadPool可能存在的一个问题是限制同时运行的线程数。CachedThreadPool不会为您限制它们,因此您可能需要编写自己的代码以确保您不会运行太多线程。这实际上取决于应用程序的设计以及如何将任务提交给执行者服务。


1
“一个CachedThreadPool正是您应使用的情况,因为对于长时间运行的线程使用它不会带来负面影响”。我不同意。CachedThreadPool动态创建无上限的线程。在大量线程上长时间运行的任务可能会占用所有资源。同样,拥有更多的线程而不是理想的线程会导致在这些线程的上下文切换上浪费过多的资源。尽管您在答案的末尾说明了需要进行自定义限制,但是答案的开头还是有点误导。
Nishit

1
为什么不简单地创建一个有界的ThreadPoolExecutor喜欢ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())
Abhijit Sarkar,

45

双方FixedThreadPoolCachedThreadPool在高负荷的应用弊端。

CachedThreadPool 比这更危险 FixedThreadPool

如果您的应用程序负载高并且要求低延迟,则由于以下缺点,最好不要使用这两个选项

  1. 任务队列的无限性质:可能导致内存不足或高延迟
  2. 长时间运行的线程将导致CachedThreadPool在创建线程时失去控制

既然您都知道这两者都是邪恶,那么较小的邪恶就没有好处。最好使用ThreadPoolExecutor,它可以对许多参数进行精细控制。

  1. 将任务队列设置为有界队列以更好地控制
  2. 拥有正确的RejectionHandler-JDK提供的您自己的RejectionHandler或Default处理程序
  3. 如果您要在完成任务之前/之后要做的事情,请覆盖beforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable)
  4. 如果需要线程自定义,则覆盖ThreadFactory
  5. 在运行时动态控制线程池大小(相关的SE问题:动态线程池

如果有人决定使用commonPool怎么办?
Crosk Cool

1
@Ravindra-您已经很好地解释了CachedThreadPool和FixedThreadPool的缺点。这表明您对并发包有了深刻的了解。
阿亚斯坎特(Ayaskant)

5

所以我有一个程序产生线程(〜5-150)来执行一堆任务。

您确定您了解所选操作系统和硬件实际上如何处理线程吗?Java如何将线程映射到OS线程,如何将线程映射到CPU线程等?我问是因为在一个JRE中创建150个线程只有在下面有大量CPU内核/线程时才有意义,这很可能不是这种情况。根据使用的操作系统和RAM,创建超过n个线程甚至可能会由于OOM错误而导致JRE终止。因此,您应该真正区分线程和线程要执行的工作,您甚至能够处理多少工作等。

这就是CachedThreadPool的问题:在实际上无法运行的线程中排队长时间运行的工作没有意义,因为您只有2个CPU内核能够处理这些线程。如果最终有150个调度线程,则可能会为Java和OS中使用的调度程序同时处理它们带来很多不必要的开销。如果您只有2个CPU内核,这是根本不可能的,除非您的线程一直一直在等待I / O。但是即使在这种情况下,很多线程也会创建很多I / O。

使用2 + n个线程创建的FixedThreadPool不会出现该问题,其中n当然是合理的低,因为使用硬件和OS资源以更少的开销来管理无论如何都无法运行的线程。


有时没有更好的选择,您可能只有1个CPU内核,但是如果您正在运行的服务器上,每个用户请求都会触发一个线程来处理请求,那么就没有其他合理的选择,特别是如果您计划扩大用户群后即可扩展服务器。
Michel Feinstein

@mFeinstein如果可以选择一个线程池实现,人们怎能选择呢?在您的具有1个CPU内核的示例中,仅产生更多线程根本没有任何意义,它完全适合我使用FixedThreadPool的示例。这也很容易扩展,首先使用一个或两个工作线程,然后根据核心数量增加10或15个线程。
ThorstenSchöning17年

2
绝大多数的Web服务器实现都会为每个新的HTTP请求创建一个新的线程……他们不会在乎计算机有多少个实际核心,这使实现更简单,更容易扩展。这适用于许多其他设计,在这些设计中,您只需要编写一次代码并进行部署,而不必在更改计算机(可能是云实例)的情况下重新编译和重新部署。
Michel Feinstein

@mFeinstein大多数Web服务器自己使用线程池来处理请求,这仅仅是因为产生无法运行的线程没有意义,或者它们使用事件循环进行连接并随后在池中处理请求。另外,您遗漏了这一点,那就是问题在于,是否能够选择正确的线程池,并且生成无法始终运行的线程仍然没有意义。根据内核规模,将FixedthreadPool配置为每台计算机合理数量的线程可扩展。
ThorstenSchöning'17

3
@ThorstenSchöning在2核计算机上拥有50个CPU绑定线程是无济于事的。在两核计算机上具有50个IO绑定线程可能会非常有帮助。
Paul Draper
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.