ThreadPoolExecutor
基本构造函数如下:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
当然还有其他的构造函数,但是都会包含以上参数。其他参数内容不是本篇主要想表述内容。
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 和 TimeUnit 表示空闲线程最长存活时间
BolckingQueue 任务缓存队列本文主要想描述的内容
运行规则
想要进一步理解,最重要的一步就是理解下述内容:
Any BlockingQueue
may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:
- If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
- If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
- If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.
稍微翻一下:
任何 BlockingQueue 都能够用来缓存提条的任务,使用方式和线程池大小相关
1. 如果小于corePoolSize的线程数量正在运行,那么会直接创建一个线程运行任务,而不是将任务放入队列。
2. 如果大于等于corePoolSize个线程正在运行,那么会将任务放入缓存队列,而不是直接启动线程执行任务。
3. 如果放入缓存队列被拒绝,那么判断当前线程数是否已经超过maximumPoolSize,如果没有,那么生成线程运行,如果超过,那么任务会被拒绝。
了解了以上三条规则之后,再看看三种BlockingQueue
SynchronousQueue 异常特殊的一个BlockingQueue, 它本身不保存数据,单独调用take和put都会造成线程阻塞,仅当成对出现才能使两个线程正常运行。单独调用offer会永远返回false(这是关键,直接造成向其中添加元素失败),仅当take的线程阻塞之后,offer才会返回true。
LinkedBlockingQueue 没有的大小限制的BlockingQueue
ArrayBlockingQueue 有大小限制的BlockingQueue
三种BlockingQueue的使用结果
- Direct handoffs. A good default choice for a work queue is a
SynchronousQueue
that hands off tasks to threads without otherwise holding them. Here, an attempt to queue a task will fail if no threads are immediately available to run it, so a new thread will be constructed. This policy avoids lockups when handling sets of requests that might have internal dependencies. Direct handoffs generally require unbounded maximumPoolSizes to avoid rejection of new submitted tasks. This in turn admits the possibility of unbounded thread growth when commands continue to arrive on average faster than they can be processed.
直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集合时出现锁定。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
- Unbounded queues. Using an unbounded queue (for example a
LinkedBlockingQueue
without a predefined capacity) will cause new tasks to wait in the queue when all corePoolSize threads are busy. Thus, no more than corePoolSize threads will ever be created. (And the value of the maximumPoolSize therefore doesn't have any effect.) This may be appropriate when each task is completely independent of others, so tasks cannot affect each others execution; for example, in a web page server. While this style of queuing can be useful in smoothing out transient bursts of requests, it admits the possibility of unbounded work queue growth when commands continue to arrive on average faster than they can be processed.
无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙的情况下将新任务加入队列。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
- Bounded queues. A bounded queue (for example, an
ArrayBlockingQueue
) helps prevent resource exhaustion when used with finite maximumPoolSizes, but can be more difficult to tune and control. Queue sizes and maximum pool sizes may be traded off for each other: Using large queues and small pools minimizes CPU usage, OS resources, and context-switching overhead, but can lead to artificially low throughput. If tasks frequently block (for example if they are I/O bound), a system may be able to schedule time for more threads than you otherwise allow. Use of small queues generally requires larger pool sizes, which keeps CPUs busier but may encounter unacceptable scheduling overhead, which also decreases throughput.
有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。