Executors.newCachedThreadPool()与Executors.newFixedThreadPool()


Answers:


202

我认为文档很好地解释了这两个功能的区别和用法:

newFixedThreadPool

创建一个线程池,该线程池重用在共享的无边界队列上运行的固定数量的线程。在任何时候,最多nThreads个线程都是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到某个线程可用为止。如果在关闭之前执行过程中由于执行失败导致任何线程终止,则在执行后续任务时将使用新线程代替。池中的线程将一直存在,直到明确将其关闭。

newCachedThreadPool

创建一个线程池,该线程池根据需要创建新线程,但是将在先前构造的线程可用时重用它们。这些池通常将提高执行许多短期异步任务的程序的性能。如果可用,执行调用将重用以前构造的线程。如果没有可用的现有线程,则将创建一个新线程并将其添加到池中。六十秒内未使用的线程将终止并从缓存中删除。因此,保持空闲时间足够长的池不会消耗任何资源。请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但细节不同(例如,超时参数)的池。

在资源方面,它newFixedThreadPool将使所有线程一直运行,直到明确终止它们为止。在newCachedThreadPool60秒钟内未使用的线程被终止并从缓存中删除。

在这种情况下,资源消耗将在很大程度上取决于情况。例如,如果您有大量长时间运行的任务,我建议您使用FixedThreadPool。至于CachedThreadPool,文档说:“这些池通常可以提高执行许多短暂的异步任务的程序的性能”。


1
是的,我已经阅读了文档..问题是... fixedThreadPool导致内存不足错误@ 3个线程..作为cachedPool在内部仅创建一个线程的原因..在增加堆大小时,我得到的是相同的两者的表现..还有其他我想念的!!
hakish

1
您是否向ThreadPool提供任何Threadfactory?我的猜测是,可能会将某些状态存储在未进行垃圾收集的线程中。如果不是,则您的程序可能正在以接近堆限制的大小运行,以至于创建3个线程会导致OutOfMemory。另外,如果cachedPool在内部仅创建一个线程,则可能表明您的任务正在同步运行。
bruno conde

@brunoconde正如@Louis F.指出的那样,这newCachedThreadPool可能会导致一些严重的问题,因为您将所有控制权留给了,thread pool并且当服务与同一主机中的其他服务一起工作时,这可能会导致其他由于长时间的CPU等待而崩溃。因此,我认为newFixedThreadPool在这种情况下可以更加安全。这篇文章也阐明了它们之间最突出的区别。
聆听

75

为了回答其他问题,我想引用Joshua Bloch撰写的有效Java,第二版,第10章,第68项:

“为特定应用程序选择执行程序服务可能很棘手。如果您正在编写小型程序轻负载服务器,则使用Executors。new-CachedThreadPool通常是一个不错的选择,因为它不需要配置,并且通常“不需要正确的事。” 但是对于高负载的生产服务器来说,缓存线程池不是一个不错的选择

高速缓存的线程池中提交的任务不排队,而是立即移交给线程执行。如果没有可用的线程,则会创建一个新线程。如果服务器负载如此之重,以至于其所有CPU都已被充分利用,并且有更多任务到来,则会创建更多线程,这只会使情况变得更糟。

因此,在负载很重的生产服务器中,最好使用Executors.newFixedThreadPool(它为您提供具有固定线程数的池),或者直接使用ThreadPoolExecutor类以实现最大控制。


15

如果您查看源代码,您将看到它们正在调用ThreadPoolExecutor。内部并设置其属性。您可以创建自己的一个,以更好地控制您的需求。

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>());
}

1
恰恰是,具有合理上限的高速缓存线程执行器,并说5-10分钟的空闲收割对于大多数情况而言是完美的。
Agoston Horvath

12

如果您不担心可调用/可运行任务的无限队列,可以使用其中之一。正如布鲁诺的建议,我也更喜欢newFixedThreadPoolnewCachedThreadPool了这两个。

ThreadPoolExecutor的 相比,提供了更灵活的功能无论是newFixedThreadPoolnewCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

优点:

  1. 您可以完全控制BlockingQueue的大小。与前两个选项不同,它不是无限的。当系统中出现意外的动荡时,由于大量待处理的Callable / Runnable任务的堆积,我不会出现内存不足的错误。

  2. 您可以实施自定义拒绝处理策略,也可以使用以下策略之一:

    1. 默认情况下ThreadPoolExecutor.AbortPolicy,处理程序在拒绝时引发运行时RejectedExecutionException。

    2. 在中ThreadPoolExecutor.CallerRunsPolicy,调用执行自己的线程运行任务。这提供了一种简单的反馈控制机制,将降低新任务的提交速度。

    3. 在中ThreadPoolExecutor.DiscardPolicy,简单地删除了无法执行的任务。

    4. 在中ThreadPoolExecutor.DiscardOldestPolicy,如果未关闭执行程序,则将丢弃工作队列开头的任务,然后重试执行(这可能再次失败,从而导致重复执行此操作)。

  3. 您可以为以下用例实现自定义线程工厂:

    1. 设置更具描述性的线程名称
    2. 设置线程守护程序状态
    3. 设置线程优先级

11

没错,Executors.newCachedThreadPool()对于服务于多个客户端和并发请求的服务器代码而言,这不是一个很好的选择。

为什么?基本上有两个(相关)问题:

  1. 它是无限的,这意味着您只需向服务中注入更多的工作(DoS攻击),就可以为任何人削弱JVM敞开大门。线程会消耗不可忽略的内存量,并且还会根据正在进行的工作增加内存消耗,因此以这种方式对服务器进行推倒很容易(除非您安装了其他断路器)。

  2. 执行器以a开头的事实加剧了这个无穷无尽的问题,SynchronousQueue这意味着在任务提供者和线程池之间存在直接切换。如果所有现有线程都忙,则每个新任务都会创建一个新线程。对于服务器代码而言,这通常是一个错误的策略。当CPU饱和时,现有任务将花费更长的时间才能完成。提交的任务更多,创建的线程更多,因此任务花费的时间越来越长。当CPU饱和时,服务器绝对不需要更多线程。

这是我的建议:

使用固定大小的线程池Executors.newFixedThreadPoolThreadPoolExecutor。具有设定的最大线程数;


6

ThreadPoolExecutor类是由许多的返回执行人基实现Executors工厂方法。因此,让我们从的角度探讨固定线程池和缓存线程池ThreadPoolExecutor

线程池执行器

该类的主要构造函数如下所示:

public ThreadPoolExecutor(
                  int corePoolSize,
                  int maximumPoolSize,
                  long keepAliveTime,
                  TimeUnit unit,
                  BlockingQueue<Runnable> workQueue,
                  ThreadFactory threadFactory,
                  RejectedExecutionHandler handler
)

核心池大小

corePoolSize确定目标线程池的最小大小。即使没有任务要执行,该实现也将保持该大小的池。

最大泳池大小

maximumPoolSize是,可以一次活动线程的最大数目。

在线程池增长并变得大于corePoolSize阈值之后,执行程序可以终止空闲线程并corePoolSize再次到达。如果allowCoreThreadTimeOut为true,则执行程序甚至可以在核心池线程闲置超过keepAliveTime阈值时终止它们。

因此,最重要的是,如果线程保持闲置状态超过keepAliveTime阈值,则由于没有需求,它们可能会被终止。

排队

当一个新任务进入并且所有核心线程都被占用时会发生什么?新任务将在该BlockingQueue<Runnable>实例中排队。当线程空闲时,可以处理那些排队的任务之一。

有在不同的实现BlockingQueue在Java接口,这样我们就可以实现不同的排队方法,如:

  1. 有界队列:新任务将在有界任务队列中排队。

  2. 无限队列:新任务将在无限任务队列中排队。因此,此队列可以增长到堆大小允许的最大范围。

  3. 同步切换:我们还可以使用SynchronousQueue将新任务排队。在这种情况下,当排队一个新任务时,另一个线程必须已经在等待该任务。

工作提交

这是ThreadPoolExecutor执行新任务的方式:

  1. 如果corePoolSize正在运行的线程数少于该线程,则尝试以给定任务为第一任务来启动新线程。
  2. 否则,它将尝试使用BlockingQueue#offer方法使新任务入队 。offer如果队列已满并立即返回,则该方法不会阻塞false
  3. 如果无法将新任务放入队列(即offerreturn false),那么它将尝试以该任务为第一任务将新线程添加到线程池中。
  4. 如果无法添加新线程,则执行器将关闭或饱和。无论哪种方式,都会使用提供的拒绝新任务RejectedExecutionHandler

固定线程池和缓存线程池之间的主要区别归结为以下三个因素:

  1. 核心池大小
  2. 最大泳池大小
  3. 排队
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| 泳池类型| 核心尺寸 最大尺寸 排队策略|
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| 固定| n(固定)| n(固定)| 无限制的`LinkedBlockingQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| 已缓存 0 | Integer.MAX_VALUE | `SynchronousQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +


固定线程池


下面是如何Excutors.newFixedThreadPool(n)工作:

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

如你看到的:

  • 线程池大小是固定的。
  • 如果需求很高,它将不会增长。
  • 如果线程空闲了相当长的一段时间,它将不会收缩。
  • 假设所有这些线程都被一些长时间运行的任务占用,并且到达率仍然很高。由于执行程序正在使用无限制队列,因此它可能会消耗堆的很大一部分。不幸的是,我们可能会遇到OutOfMemoryError

什么时候应该使用其中一个?就资源利用而言,哪种策略更好?

当为了资源管理目的而限制并发任务的数量时,固定大小的线程池似乎是一个不错的选择

例如,如果我们要使用执行程序来处理Web服务器请求,则固定执行程序可以更合理地处理请求突发。

为了更好地进行资源管理,强烈建议创建一个ThreadPoolExecutor具有有限BlockingQueue<T>实现并结合合理的定制RejectedExecutionHandler


缓存线程池


下面是如何Executors.newCachedThreadPool()工作:

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

如你看到的:

  • 线程池可以从零个线程增长到Integer.MAX_VALUE。实际上,线程池是无界的。
  • 如果任何线程空闲超过1分钟,它可能会终止。因此,如果线程保持太多空闲,则池可以缩小。
  • 如果在执行新任务时占用了所有分配的线程,那么它将创建一个新线程,因为SynchronousQueue在另一端没有人接受新任务时,向其提供新任务总是失败!

什么时候应该使用其中一个?就资源利用而言,哪种策略更好?

当您有许多可预测的短期任务时,请使用它。



1

我进行了一些快速测试,并得出以下发现:

1)如果使用SynchronousQueue:

线程达到最大大小后,任何新工作都会被拒绝,但以下情况除外。

线程“主”中的异常java.util.concurrent.RejectedExecutionException:从java.util.concurrent.ThreadPoolExecutor@5acf9800拒绝了任务java.util.concurrent.FutureTask@3fee733d [正在运行,池大小= 3,活动线程= 3,排队的任务= 0,已完成的任务= 0]

在java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)

2)如果使用LinkedBlockingQueue:

线程永远不会从最小大小增加到最大大小,这意味着线程池是固定大小的最小大小。

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.