FixedThreadPool vs CachedThreadPool:两个邪恶中较小的一个

Dan*_*iel 85 java multithreading executorservice java.util.concurrent threadpool

所以我有一个程序产生线程(~5-150)执行一堆任务.最初我使用的是FixedThreadPool因为这个类似的问题表明它们更适合长寿命的任务,而且由于我对多线程的知识非常有限,我认为线程的平均寿命(几分钟)" 长寿 ".

但是,我最近添加了生成其他线程的功能,这样做超过了我设置的线程限制.在这种情况下,最好猜测并增加我可以允许的线程数或切换到一个CachedThreadPool所以我没有浪费线程?

初步尝试它们,似乎没有什么区别,所以我倾向于选择CachedThreadPool公正以避免浪费.但是,线程的生命周期是否意味着我应该选择一个FixedThreadPool并且只处理未使用的线程?这个问题使得看起来这些额外的线程没有浪费,但我希望澄清.

Tre*_*man 103

CachedThreadPool正是您应该用于您的情况,因为对于长时间运行的线程使用一个没有负面后果.关于CachedThreadPools的Java文档中适用于简短任务的注释仅表明它们特别适用于此类情况,而不是它们不能或不应该用于涉及长时间运行任务的任务.

进一步详细说明,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>());
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • “CachedThreadPool 正是您应该在您的情况下使用的,因为将一个用于长时间运行的线程没有负面影响”。我不认为我同意。CachedThreadPool 动态创建没有上限的线程。在大量线程上长时间运行的任务可能会占用所有资源。此外,拥有比理想更多的线程会导致在这些线程的上下文切换上浪费太多资源。尽管您在答案的末尾解释了需要自定义节流,但答案的开头有点误导。 (2认同)

Rav*_*abu 39

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

CachedThreadPool 比危险更危险 FixedThreadPool

如果您的应用程序负载很高并且需要低延迟,那么由于以下缺点,最好摆脱这两个选项

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

既然你知道两者都是邪恶的,那么较小的邪恶就不会有好处.首选ThreadPoolExecutor,它提供对许多参数的精细控制.

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


Tho*_*ing 5

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

您确定了解操作系统和所选硬件实际处理线程的方式吗?Java如何将线程映射到OS线程,如何将线程映射到CPU线程等?我问,因为在ONE JRE中创建150个线程只有在下面有大量CPU核心/线程时才有意义,但很可能并非如此.根据所使用的操作系统和RAM,创建超过n个线程甚至可能导致JRE因OOM错误而终止.所以你应该真正区分这些线程的线程和工作,你甚至能够处理多少工作等等.

这就是CachedThreadPool的问题:在实际无法运行的线程中排队长时间运行的工作是没有意义的,因为你只有2个CPU内核能够处理这些线程.如果最终得到150个调度线程,则可能会为Java和OS中使用的调度程序创建大量不必要的开销,以便同时处理它们.如果你只有2个CPU核心,这是完全不可能的,除非你的线程一直在等待I/O或者这样.但即使在这种情况下,很多线程也会产生大量的I/O ......

并且使用例如2 + n个线程创建的FixedThreadPool不会发生这个问题,其中n当然是合理的低,因为使用该硬件和OS资源时,管理不能运行的线程的开销要少得多.

  • 绝大多数Web服务器实现将为每个新的HTTP请求创建一个新线程......他们不关心机器有多少实际核心,这使得实现更简单,更容易扩展.这适用于您只需要编码一次并进行部署的许多其他设计,如果您更改可能是云实例的计算机,则无需重新编译和重新部署. (2认同)
  • @ThorstenSchöning在2核计算机上拥有50个CPU绑定线程无济于事。在两核计算机上具有50个IO绑定线程可能会非常有帮助。 (2认同)