Executors.newCachedThreadPool()与Executors.newFixedThreadPool()

newCachedThreadPool()newFixedThreadPool()

我应该什么时候使用其中一个? 哪种策略在资源利用方面更好?

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

newFixedThreadPool

创build一个线程池,重用使用共享的无界队列的固定数量的线程。 在任何时候,至多nThreads线程将被主动处理任务。 如果在所有线程处于活动状态时提交其他任务,则会在队列中等待,直到线程可用。 如果任何线程在closures之前的执行期间由于失败而终止,那么如果需要执行后续任务,则将取代它。 池中的线程将一直存在,直到明确closures。

newCachedThreadPool

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

在资源方面, newFixedThreadPool将保持所有线程运行直到它们被明确终止。 在newCachedThreadPool线程中,60秒内未使用的线程被终止并从caching中移除。

鉴于此,资源消耗将非常依赖于这种情况。 例如,如果你有很多长时间运行的任务,我会build议FixedThreadPool 。 至于CachedThreadPool ,文档说“这些池通常会提高执行许多短暂asynchronous任务的程序的性能”。

为了完成其他答案,我想引用Joshua Bloch编写的Effective Java第二版第10章第68项:

“为特定的应用程序select执行程序服务可能会非常棘手,如果你正在编写一个小程序或者轻度加载的服务器 ,使用Executors.new-CachedThreadPool通常是一个不错的select ,因为它不需要configuration,正确的事情“。但是,一个caching的线程池并不是一个 负载很重的生产服务器 的好select

caching线程池中提交的任务不会排队,而是立即切换到线程执行。 如果没有线程可用,则创build一个新线程 。 如果一台服务器的负载过重,所有的CPU都被充分利用,并且有更多的任务到达,那么会创build更多的线程,这只会让事情变得更糟。

因此, 在负载很重的生产服务器中 ,使用Executors.newFixedThreadPool会更好,因为它为您提供了一个具有固定数量的线程的池,或者直接使用ThreadPoolExecutor类来进行最大程度的控制。

如果你看到grepcode中的代码,你会看到,他们正在调用ThreadPoolExecutor。 内部并设置其属性。 你可以创build一个更好地控制你的需求。

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

没错,对于服务多个客户端和并发请求的服务器代码来说, Executors.newCachedThreadPool()并不是一个好的select。

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

  1. 这是无限的,这意味着您只需向服务注入更多工作(DoS攻击),就可以为任何人打开通向JVM的门户。 线程消耗了不可忽视的内存量,并且还会因为正在进行的工作而增加内存消耗,所以这样很容易推倒服务器(除非你有其他的断路器)。

  2. Executor被SynchronousQueue ,这意味着task-giver和线程池之间有一个直接的切换,从而加剧了无限的问题。 如果所有现有线程繁忙,每个新任务将创build一个新线程。 这通常是服务器代码的一个糟糕的策略。 当CPU达到饱和时,现有的任务需要更长时间才能完成。 然而,更多的任务正在提交,更多的线程被创build,所以任务需要更长,更长的时间来完成。 当CPU饱和时,更多的线程绝对不是服务器需要的。

这是我的build议:

使用固定大小的线程池Executors.newFixedThreadPool或ThreadPoolExecutor。 带有最大数量的线程;

如果您不担心无限制的Callable / Runnable任务队列,则可以使用其中的一个。 正如bruno所build议的那样,我也比较喜欢newFixedThreadPoolnewCachedThreadPool

但是ThreadPoolExecutor比newFixedThreadPoolnewCachedThreadPool提供了更灵活的function

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

优点:

  1. 您可以完全控制BlockingQueue大小。 不像前两种select那样没有界限。 我不会因为在系统意外的动荡中大量堆积未决的Callable / Runnable任务而导致内存不足错误。

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

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

    2. ThreadPoolExecutor.CallerRunsPolicy ,调用执行本身的线程运行该任务。 这提供了一个简单的反馈控制机制,将减慢提交新任务的速度。

    3. ThreadPoolExecutor.DiscardPolicy ,无法执行的任务将被简单地删除。

    4. ThreadPoolExecutor.DiscardOldestPolicy ,如果执行程序没有closures,工作队列头部的任务将被删除,然后重试执行(可能会再次失败,导致重复执行)。

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

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

只有当您拥有Javadoc中所述的短暂asynchronous任务时,如果您提交需要较长时间处理的任务,则必须使用newCachedThreadPool,否则最终会创build太多的线程。 如果您以较快的速度将较长时间的运行任务提交到newCachedThreadPool( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ),则可能会达到100%的CPU。