newCachedThreadPool()
与 newFixedThreadPool()
什么时候应该使用其中一个?就资源利用而言,哪种策略更好?
newCachedThreadPool()
与 newFixedThreadPool()
什么时候应该使用其中一个?就资源利用而言,哪种策略更好?
Answers:
我认为文档很好地解释了这两个功能的区别和用法:
创建一个线程池,该线程池重用在共享的无边界队列上运行的固定数量的线程。在任何时候,最多nThreads个线程都是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到某个线程可用为止。如果在关闭之前执行过程中由于执行失败导致任何线程终止,则在执行后续任务时将使用新线程代替。池中的线程将一直存在,直到明确将其关闭。
创建一个线程池,该线程池根据需要创建新线程,但是将在先前构造的线程可用时重用它们。这些池通常将提高执行许多短期异步任务的程序的性能。如果可用,执行调用将重用以前构造的线程。如果没有可用的现有线程,则将创建一个新线程并将其添加到池中。六十秒内未使用的线程将终止并从缓存中删除。因此,保持空闲时间足够长的池不会消耗任何资源。请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但细节不同(例如,超时参数)的池。
在资源方面,它newFixedThreadPool
将使所有线程一直运行,直到明确终止它们为止。在newCachedThreadPool
60秒钟内未使用的线程被终止并从缓存中删除。
在这种情况下,资源消耗将在很大程度上取决于情况。例如,如果您有大量长时间运行的任务,我建议您使用FixedThreadPool
。至于CachedThreadPool
,文档说:“这些池通常可以提高执行许多短暂的异步任务的程序的性能”。
为了回答其他问题,我想引用Joshua Bloch撰写的有效Java,第二版,第10章,第68项:
“为特定应用程序选择执行程序服务可能很棘手。如果您正在编写小型程序或轻负载服务器,则使用Executors。new-CachedThreadPool通常是一个不错的选择,因为它不需要配置,并且通常“不需要正确的事。” 但是对于高负载的生产服务器来说,缓存线程池不是一个不错的选择!
在高速缓存的线程池中,提交的任务不排队,而是立即移交给线程执行。如果没有可用的线程,则会创建一个新线程。如果服务器负载如此之重,以至于其所有CPU都已被充分利用,并且有更多任务到来,则会创建更多线程,这只会使情况变得更糟。
因此,在负载很重的生产服务器中,最好使用Executors.newFixedThreadPool(它为您提供具有固定线程数的池),或者直接使用ThreadPoolExecutor类以实现最大控制。”
如果您查看源代码,您将看到它们正在调用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>());
}
如果您不担心可调用/可运行任务的无限队列,可以使用其中之一。正如布鲁诺的建议,我也更喜欢newFixedThreadPool
到newCachedThreadPool
了这两个。
但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
,调用执行自己的线程运行任务。这提供了一种简单的反馈控制机制,将降低新任务的提交速度。
在中ThreadPoolExecutor.DiscardPolicy
,简单地删除了无法执行的任务。
在中ThreadPoolExecutor.DiscardOldestPolicy
,如果未关闭执行程序,则将丢弃工作队列开头的任务,然后重试执行(这可能再次失败,从而导致重复执行此操作)。
您可以为以下用例实现自定义线程工厂:
没错,Executors.newCachedThreadPool()
对于服务于多个客户端和并发请求的服务器代码而言,这不是一个很好的选择。
为什么?基本上有两个(相关)问题:
它是无限的,这意味着您只需向服务中注入更多的工作(DoS攻击),就可以为任何人削弱JVM敞开大门。线程会消耗不可忽略的内存量,并且还会根据正在进行的工作增加内存消耗,因此以这种方式对服务器进行推倒很容易(除非您安装了其他断路器)。
执行器以a开头的事实加剧了这个无穷无尽的问题,SynchronousQueue
这意味着在任务提供者和线程池之间存在直接切换。如果所有现有线程都忙,则每个新任务都会创建一个新线程。对于服务器代码而言,这通常是一个错误的策略。当CPU饱和时,现有任务将花费更长的时间才能完成。提交的任务更多,创建的线程更多,因此任务花费的时间越来越长。当CPU饱和时,服务器绝对不需要更多线程。
这是我的建议:
使用固定大小的线程池Executors.newFixedThreadPool或 ThreadPoolExecutor。具有设定的最大线程数;
本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接口,这样我们就可以实现不同的排队方法,如:
有界队列:新任务将在有界任务队列中排队。
无限队列:新任务将在无限任务队列中排队。因此,此队列可以增长到堆大小允许的最大范围。
同步切换:我们还可以使用SynchronousQueue
将新任务排队。在这种情况下,当排队一个新任务时,另一个线程必须已经在等待该任务。
这是ThreadPoolExecutor
执行新任务的方式:
corePoolSize
正在运行的线程数少于该线程,则尝试以给定任务为第一任务来启动新线程。BlockingQueue#offer
方法使新任务入队
。offer
如果队列已满并立即返回,则该方法不会阻塞false
。offer
return 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>());
}
如你看到的:
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
。实际上,线程池是无界的。SynchronousQueue
在另一端没有人接受新任务时,向其提供新任务总是失败!什么时候应该使用其中一个?就资源利用而言,哪种策略更好?
当您有许多可预测的短期任务时,请使用它。
仅当您具有Javadoc中所述的短暂异步任务时,才必须使用newCachedThreadPool;如果提交的任务要花费较长的时间来处理,则最终将创建太多线程。如果以更快的速度向newCachedThreadPool(http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/)提交长时间运行的任务,则可能会占用100%的CPU 。
我进行了一些快速测试,并得出以下发现:
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:
线程永远不会从最小大小增加到最大大小,这意味着线程池是固定大小的最小大小。