newCachedThreadPool()
相对newFixedThreadPool()
我什么时候应该使用其中一种?在资源利用方面哪种策略更好?
newCachedThreadPool()
相对newFixedThreadPool()
我什么时候应该使用其中一种?在资源利用方面哪种策略更好?
我认为文档很好地解释了这两个函数的区别和用法:
创建一个线程池,该线程池重用在共享无界队列上运行的固定数量的线程。在任何时候,最多 nThreads 个线程将是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到有线程可用。如果任何线程在关闭之前的执行过程中由于失败而终止,如果需要执行后续任务,新的线程将取代它。池中的线程将一直存在,直到显式关闭。
创建一个线程池,根据需要创建新线程,但在可用时将重用以前构造的线程。这些池通常会提高执行许多短期异步任务的程序的性能。如果可用,对执行的调用将重用以前构造的线程。如果没有可用的现有线程,将创建一个新线程并将其添加到池中。六十秒内未使用的线程将被终止并从缓存中删除。因此,保持空闲足够长时间的池不会消耗任何资源。请注意,可以使用 ThreadPoolExecutor 构造函数创建具有相似属性但细节不同(例如超时参数)的池。
在资源方面,newFixedThreadPool
它将保持所有线程运行,直到它们被显式终止。在newCachedThreadPool
60 秒内未使用的线程将被终止并从缓存中删除。
鉴于此,资源消耗将在很大程度上取决于具体情况。例如,如果您有大量长时间运行的任务,我建议使用FixedThreadPool
. 至于CachedThreadPool
,文档说“这些池通常会提高执行许多短期异步任务的程序的性能”。
为了完成其他答案,我想引用 Joshua Bloch 撰写的 Effective Java, 2nd Edition,第 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>());
}
该类是从许多工厂方法ThreadPoolExecutor
返回的执行程序的基本实现。Executors
因此,让我们从's 的角度来处理Fixed和Cached线程池。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
接口实现,因此我们可以实现不同的排队方法,例如:
有界队列:新任务将在有界任务队列中排队。
无界队列:新任务将在无界任务队列中排队。所以这个队列可以在堆大小允许的范围内增长。
同步切换:我们也可以使用SynchronousQueue
来对新任务进行排队。在这种情况下,当排队一个新任务时,另一个线程必须已经在等待该任务。
以下是ThreadPoolExecutor
执行新任务的方式:
corePoolSize
线程正在运行,则尝试以给定任务作为其第一个作业启动一个新线程。BlockingQueue#offer
方法将新任务排入队列。如果队列已满并立即返回,该offer
方法不会阻塞false
。offer
返回false
),那么它会尝试将一个新线程添加到线程池中,并将该任务作为它的第一个作业。RejectedExecutionHandler
.固定线程池和缓存线程池之间的主要区别归结为以下三个因素:
+-----------+------------+-------+----- --------------------------------------------+ | 泳池类型 | 核心尺寸 | 最大尺寸 | 排队策略 | +-----------+------------+-------+----- --------------------------------------------+ | 固定 | 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
. 实际上,线程池是无界的。SynchronousQueue
当另一端没有人接受时,向 a 提供新任务总是失败!我什么时候应该使用其中一种?在资源利用方面哪种策略更好?
当您有很多可预测的短期任务时使用它。
如果您不担心Callable/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
中,调用 execute 的线程自己运行任务。这提供了一种简单的反馈控制机制,可以减慢提交新任务的速度。
在ThreadPoolExecutor.DiscardPolicy
中,无法执行的任务被简单地丢弃。
在ThreadPoolExecutor.DiscardOldestPolicy
中,如果执行器没有关闭,则丢弃工作队列头部的任务,然后重试执行(可能再次失败,导致重复此操作。)
您可以为以下用例实现自定义线程工厂:
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:
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).
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;
仅当您有 Javadoc 中所述的短期异步任务时才必须使用 newCachedThreadPool,如果您提交需要较长时间处理的任务,您最终会创建太多线程。如果您以更快的速度向 newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ) 提交长时间运行的任务,您可能会达到 100% CPU。
我做了一些快速测试并有以下发现:
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:
线程永远不会从最小大小增加到最大大小,这意味着线程池的大小固定为最小大小。