187

newCachedThreadPool()相对newFixedThreadPool()

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

4

8 回答 8

225

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

newFixedThreadPool

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

newCachedThreadPool

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

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

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

于 2009-06-04T09:25:25.213 回答
85

为了完成其他答案,我想引用 Joshua Bloch 撰写的 Effective Java, 2nd Edition,第 10 章,第 68 项:

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

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

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

于 2015-12-15T09:21:44.317 回答
22

如果您查看源代码,您会看到,它们正在调用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>());
}
于 2015-09-06T17:41:00.930 回答
15

该类是从许多工厂方法ThreadPoolExecutor返回的执行程序的基本实现。Executors因此,让我们从's 的角度来处理FixedCached线程池。ThreadPoolExecutor

线程池执行器

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

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

核心池大小

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

最大池大小

maximumPoolSize是一次可以激活的最大线程数。

在线程池增长并大于corePoolSize阈值后,执行器可以终止空闲线程并再次到达corePoolSize。如果allowCoreThreadTimeOut为真,那么执行器甚至可以在核心池线程空闲超过keepAliveTime阈值时终止它们。

所以底线是如果线程保持空闲超过keepAliveTime阈值,它们可能会被终止,因为没有对它们的需求。

排队

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

Java中有不同的BlockingQueue接口实现,因此我们可以实现不同的排队方法,例如:

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

  2. 无界队列:新任务将在无界任务队列中排队。所以这个队列可以在堆大小允许的范围内增长。

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

作品提交

以下是ThreadPoolExecutor执行新任务的方式:

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

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

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


固定线程池


工作原理如下Excutors.newFixedThreadPool(n)

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

如你看到的:

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

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

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

例如,如果我们要使用 executor 来处理 web 服务器请求,固定的 executor 可以更合理地处理请求突发。

为了更好的资源管理,强烈建议创建一个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当另一端没有人接受时,向 a 提供新任务总是失败!

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

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

于 2019-11-08T17:34:40.420 回答
15

如果您不担心Callable/Runnable任务的无限队列,您可以使用其中之一。正如布鲁诺所建议的那样,我也更喜欢newFixedThreadPoolnewCachedThreadPool两个。

但是ThreadPoolExecutor 提供了更灵活的newFixedThreadPool特性newCachedThreadPool

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中,调用 execute 的线程自己运行任务。这提供了一种简单的反馈控制机制,可以减慢提交新任务的速度。

    3. ThreadPoolExecutor.DiscardPolicy中,无法执行的任务被简单地丢弃。

    4. ThreadPoolExecutor.DiscardOldestPolicy中,如果执行器没有关闭,则丢弃工作队列头部的任务,然后重试执行(可能再次失败,导致重复此操作。)

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

    1. 设置更具描述性的线程名称
    2. 设置线程守护进程状态
    3. 设置线程优先级
于 2015-11-28T16:51:53.627 回答
14

That’s right, Executors.newCachedThreadPool() isn't a great choice for server code that's servicing multiple clients and concurrent requests.

Why? There are basically two (related) problems with it:

  1. It's unbounded, which means that you're opening the door for anyone to cripple your JVM by simply injecting more work into the service (DoS attack). Threads consume a non-negligible amount of memory and also increase memory consumption based on their work-in-progress, so it's quite easy to topple a server this way (unless you have other circuit-breakers in place).

  2. The unbounded problem is exacerbated by the fact that the Executor is fronted by a SynchronousQueue which means there's a direct handoff between the task-giver and the thread pool. Each new task will create a new thread if all existing threads are busy. This is generally a bad strategy for server code. When the CPU gets saturated, existing tasks take longer to finish. Yet more tasks are being submitted and more threads created, so tasks take longer and longer to complete. When the CPU is saturated, more threads is definitely not what the server needs.

Here are my recommendations:

Use a fixed-size thread pool Executors.newFixedThreadPool or a ThreadPoolExecutor. with a set maximum number of threads;

于 2015-08-25T07:52:27.773 回答
5

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

于 2014-05-08T18:43:19.003 回答
1

我做了一些快速测试并有以下发现:

1) 如果使用同步队列:

线程达到最大大小后,任何新工作都将被拒绝,但如下所示。

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

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

2) 如果使用 LinkedBlockingQueue:

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

于 2018-03-12T21:18:58.593 回答