newCachedThreadPool()
和 newFixedThreadPool()
,何时使用哪一个呢?从资源利用角度来看,哪种策略更好呢?
我认为文档已经很好地解释了这两个函数的区别和用法:
创建一个线程池来重复使用一定数量的线程,这些线程在共享的无限队列上操作。在任何时候,最多只有 nThreads 线程会活跃地处理任务。如果所有线程都被占用时又提交了额外的任务,则它们将在队列中等待,直到有可用的线程。如果任何线程在关闭之前由于执行期间的故障而终止,则如果需要执行后续任务,将有新的线程取代该线程。线程池中的线程将一直存在,直到它明确停止。
创建一个线程池,在需要时创建新线程,但会重用先前构建的线程(如果有)。这些池通常会提高执行许多短暂异步任务的程序性能。对 execute 的调用将重用先前构建的线程(如果有)。如果没有现有线程可用,则会创建一个新线程并将其添加到池中。未使用60秒的线程将被终止并从缓存中移除。因此,长时间空闲的池不会消耗任何资源。请注意,可以使用 ThreadPoolExecutor 构造函数创建具有类似属性但不同细节(例如超时参数)的池。
关于资源方面,newFixedThreadPool
将保持所有线程一直运行,直到它们明确终止。在 newCachedThreadPool
中,未使用60秒的线程将被终止并从缓存中移除。
因此,资源消耗将在很大程度上取决于情况。例如,如果您有大量长时间运行的任务,我建议使用FixedThreadPool
。 至于CachedThreadPool
,文档中指出:“这些池通常会提高执行许多短期异步任务的程序的性能”。
为了补充其他答案,我想引用Joshua Bloch在他的书《Effective Java》第二版中的第10章第68条:
"为特定应用程序选择执行器服务可能很棘手。如果您正在编写一个小程序或一个轻负载服务器,使用Executors.newCachedThreadPool通常是一个不错的选择,因为它不需要配置并且通常“做正确的事情”。但是,对于负载繁重的生产服务器来说,缓存线程池并不是一个好的选择!
在缓存线程池中,提交的任务不会排队,而是立即移交给一个线程进行执行。如果没有可用的线程,则会创建一个新线程。如果服务器过载以至于所有CPU都被完全利用,并且更多任务到达,则会创建更多线程,这只会使问题变得更糟。
因此,在负载繁重的生产服务器中,最好使用Executors.newFixedThreadPool来获得具有固定线程数的线程池,或直接使用ThreadPoolExecutor类进行最大控制。"
如果您在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>());
}
ThreadPoolExecutor
类是许多Executors
工厂方法返回的执行器的基本实现。因此,让我们从ThreadPoolExecutor
的角度来看待Fixed和Cached线程池。
该类的主构造函数如下所示:
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>
实例中排队。当线程变为空闲时,可以处理其中一个排队的任务。
Java 中有不同的 BlockingQueue
接口实现,因此我们可以实现不同的排队方法,如:
有界队列: 新任务将排队在一个有界任务队列中。
无界队列: 新任务将排队在一个无界任务队列中。因此,该队列可以根据堆大小的限制而增长。
同步移交: 我们也可以使用 SynchronousQueue
来排队新任务。在这种情况下,当排队新任务时,另一个线程必须已经在等待该任务。
以下是 ThreadPoolExecutor
如何执行新任务:
corePoolSize
,则尝试启动一个新线程,并以给定任务作为其第一个作业。BlockingQueue#offer
方法将新任务排队。如果队列已满,则 offer
方法不会阻塞并立即返回 false
。offer
返回 false
),则尝试向线程池添加一个新线程,并以此任务作为其第一个作业。RejectedExecutionHandler
拒绝新任务。固定线程池和缓存线程池之间的主要区别在于这三个因素:
+-----------+-----------+-------------------+---------------------------------+ | 池类型 | 核心大小 | 最大大小 | 排队策略 | +-----------+-----------+-------------------+---------------------------------+ | 固定 | 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>());
}
正如您所看到的:
我应该在何时使用其中之一?哪种策略在资源利用方面更好?
当我们要限制并发任务数量以进行资源管理时,固定大小的线程池似乎是一个不错的选择。
例如,如果我们要使用执行程序处理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
。实际上,线程池是无限制的。SynchronousQueue
提供新任务总是失败的!什么情况下应使用其中之一?哪种策略在资源利用方面更好?
当您有大量可预测的短期运行任务时使用它。
如果您不担心无限队列的Callable/Runnable任务,可以使用其中之一。正如bruno所建议的那样,我也更喜欢newFixedThreadPool
而不是这两个中的任何一个。
但是,无限队列大小总是很危险的。如果系统中出现了一些意外的动荡,当前线程被卡住,队列大小将增加,并可能导致内存溢出错误或系统性能下降。
但是ThreadPoolExecutor相对于newFixedThreadPool
或newCachedThreadPool
提供了更灵活的功能。它提供了对各种属性的更细粒度的控制。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
优点:
您可以完全控制BlockingQueue的大小。与前两个选项不同,它不是无限制的。当系统出现意外波动时,由于挂起的Callable/Runnable任务堆积过多而导致内存溢出错误的情况将不会发生。
您可以实现自定义的拒绝处理策略或使用以下策略之一:
- 在默认的
ThreadPoolExecutor.AbortPolicy
中,处理程序在拒绝时抛出运行时RejectedExecutionException异常。
- 在
ThreadPoolExecutor.CallerRunsPolicy
中,调用execute方法的线程本身运行任务。这提供了一个简单的反馈控制机制,可以减缓提交新任务的速率。
- 在
ThreadPoolExecutor.DiscardPolicy
中,无法执行的任务将被简单地丢弃。
- 在
ThreadPoolExecutor.DiscardOldestPolicy
中,如果执行器没有关闭,则删除工作队列头部的任务,然后重试执行(可能会再次失败,从而导致重复执行)。
您可以为以下用例实现自定义线程工厂:
通过提供不同的ThreadFactory,您可以更改线程的名称、线程组、优先级、守护进程状态等。
没错,Executors.newCachedThreadPool()
不是为服务多个客户端和并发请求的服务器代码而设计的最佳选择。
为什么呢?主要有以下两个(相关)问题:
它没有限制,这意味着任何人都可以通过向服务注入更多工作来使您的JVM崩溃(DoS攻击)。线程消耗了相当大的内存,并且根据其正在进行的工作增加内存消耗,因此很容易以这种方式使服务器崩溃(除非您还有其他断路器)。
未受限制的问题还被执行程序前面的SynchronousQueue
所加剧,这意味着任务提供者和线程池之间有直接交接。如果所有现有线程都忙,则每个新任务都会创建一个新线程。这通常不适用于服务器代码的策略。当CPU饱和时,现有任务需要更长时间才能完成。然而,越来越多的任务正在提交,越来越多的线程被创建,因此任务需要越来越长的时间才能完成。当CPU饱和时,服务器绝对不需要更多的线程。
下面是我的建议:
使用具有设置最大线程数的固定大小线程池 Executors.newFixedThreadPool 或 ThreadPoolExecutor.
根据Javadoc的说明,只有在处理短暂异步任务时才应使用newCachedThreadPool,如果您提交需要较长时间处理的任务,则会创建过多的线程。如果您以更快的速度向newCachedThreadPool提交长时间运行的任务,则可能会达到100%的CPU使用率(http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/)。
newCachedThreadPool
可能会导致一些严重问题,因为您将所有控制权留给了线程池
,而当 服务 与同一 主机 中的其他服务一起工作时,由于长时间的 CPU 等待,可能会导致其他服务崩溃。因此,在这种情况下,我认为newFixedThreadPool
更安全。此外,这篇文章澄清了它们之间最显着的区别。 - Hearen