bru*_*nde 182

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

newFixedThreadPool

创建一个线程池,该线程池重用在共享的无界队列中运行的固定数量的线程.在任何时候,最多nThreads线程将是活动的处理任务.如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到线程可用.如果任何线程由于在关闭之前执行期间的故障而终止,则在需要执行后续任务时将使用新的线程.池中的线程将一直存在,直到它被明确关闭.

newCachedThreadPool

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

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

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


Lou*_* F. 66

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

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

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

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


Ali*_*ani 13

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

线程池执行器

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

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

核心池大小

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

最大池大小

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

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

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

排队

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

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

  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)工作原理:


+-----------+-----------+-------------------+---------------------------------+
| Pool Type | Core Size |    Maximum Size   |         Queuing Strategy        |
+-----------+-----------+-------------------+---------------------------------+
|   Fixed   | n (fixed) |     n (fixed)     | Unbounded `LinkedBlockingQueue` |
+-----------+-----------+-------------------+---------------------------------+
|   Cached  |     0     | Integer.MAX_VALUE |        `SynchronousQueue`       |
+-----------+-----------+-------------------+---------------------------------+

如你看到的:

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

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

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

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

为了更好的资源管理,强烈建议创建一个ThreadPoolExecutor有界BlockingQueue<T>实现和合理的RejectedExecutionHandler.


缓存线程池


这是Executors.newCachedThreadPool()工作原理:

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

如你看到的:

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

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

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

  • 感谢这篇文章。我希望这篇文章能有更多的票数,这样它就能上升^^^ 在做出决定时总是能更好地理解背后的机制。 (2认同)

krm*_*007 12

如果你看到grepcode中的代码,你会看到,他们正在调用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>());
}
Run Code Online (Sandbox Code Playgroud)


Rav*_*abu 9

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

ThreadPoolExecutor的 规定相比,它更灵活的功能newFixedThreadPoolnewCachedThreadPool

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

好处:

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

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

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

    2. ThreadPoolExecutor.CallerRunsPolicy,调用execute本身的线程运行任务.这提供了一种简单的反馈控制机制,可以降低新任务的提交速度.

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

    4. ThreadPoolExecutor.DiscardOldestPolicy,如果执行程序未关闭,则删除工作队列头部的任务,然后重试执行(可能再次失败,导致重复执行).

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

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


小智 8

这是正确的,Executors.newCachedThreadPool()对于为多个客户端和并发请求提供服务的服务器代码来说,它不是一个很好的选择.

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

  1. 它是无界限的,这意味着你只需要在服务中注入更多的工作(DoS攻击),就可以让任何人瘫痪你的JVM.线程消耗不可忽略的内存量,并且还会根据正在进行的工作增加内存消耗,因此以这种方式推倒服务器非常容易(除非您有其他断路器).

  2. Executor面向一个事实会加剧无限问题,SynchronousQueue这意味着任务提供者和线程池之间存在直接切换.如果所有现有线程都忙,则每个新任务都将创建一个新线程.这通常是服务器代码的糟糕策略.当CPU饱和时,现有任务需要更长时间才能完成.然而,正在提交更多任务并创建更多线程,因此任务需要更长时间才能完成.当CPU饱和时,更多线程绝对不是服务器所需要的.

以下是我的建议:

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


Dha*_*raj 5

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