一、ThreadPoolExecutor 的重要参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
-
corePoolSize(核心线程数):
- 核心线程会一直存活,即使没有任务需要执行。
- 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理任务。
- 设置
allowCoreThreadTimeout=true
(默认false
)时,核心线程会超时关闭。 - 根据任务类型(CPU 密集型或 IO 密集型)以及机器的 CPU 核数来设置。
- CPU 密集型:需要大量的CPU计算,所以
corePoolSize = CPU 核数 + 1
。 - IO 密集型:需要去到磁盘进行读取,速度会比CPU运算更快
corePoolSize = CPU 核数 * 2
。
- CPU 密集型:需要大量的CPU计算,所以
-
maximumPoolSize(最大线程数):
- 当线程数大于等于核心线程数且任务队列已满时,线程池会创建新线程来处理任务。
- 当线程数等于最大线程数且任务队列已满时,线程池会拒绝处理任务而抛出异常。
-
keepAliveTime(线程空闲时间):
- 当线程空闲时间达到
keepAliveTime
时,线程会退出,直到线程数量等于核心线程数。如果allowCoreThreadTimeout=true
,则会直到线程数量为 0。
- 当线程空闲时间达到
-
queueCapacity(任务队列容量,阻塞队列):
- 当核心线程数达到最大时,新任务会放在队列中排队等待执行。
-
allowCoreThreadTimeout(允许核心线程超时):
- 控制核心线程是否可以超时关闭。
-
rejectedExecutionHandler(任务拒绝处理器):
- 当线程数已经达到最大线程数且队列已满,或者线程池被调用
shutdown()
后提交任务,会拒绝新任务。 - ThreadPoolExecutor 采用策略设计模式处理拒绝任务的场景,有多种策略可供选择:
AbortPolicy
:丢弃任务,抛运行时异常。CallerRunsPolicy
:执行任务。DiscardPolicy
:忽视,什么都不会发生。DiscardOldestPolicy
:从队列中踢出最先进入队列(最后一个执行)的任务。
- 当线程数已经达到最大线程数且队列已满,或者线程池被调用
二、如何设置参数
-
首先确定几个值:
tasks
:每秒的任务数,假设为 500 - 1000。taskcost
:每个任务花费时间,假设为 0.1s。responsetime
:系统允许容忍的最大响应时间,假设为 1s。
-
计算核心线程数:
threadcount = tasks / (1 / taskcost) = tasks * taskcost = (500 ~ 1000) * 0.1 = 50 ~ 100
个线程。核心线程数应大于 50,根据 8020 原则,如果 80%的每秒任务数小于 800,那么核心线程数设置为 80 即可。
-
计算任务队列容量:
queueCapacity = (coreSizePool / taskcost) * responsetime
。例如,核心线程数为 80 时,队列容量为 80/0.1*1 = 800。这意味着队列里的线程可以等待 1s,超过了的需要新开线程来执行。
-
最大线程数在生产环境上往往设置成与核心线程数一样,这样可以减少在处理过程中创建线程的开销。
-
rejectedExecutionHandler
根据具体情况来决定任务拒绝策略,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理。 -
keepAliveTime
和allowCoreThreadTimeout
采用默认通常能满足。实际情况要根据机器性能来调整,如果在未达到最大线程数的情况机器 CPU load 已经满了,则需要通过升级硬件和优化代码,降低taskcost
来处理。
三、线程池队列的选择
-
无界队列:
- 常用的为无界的
LinkedBlockingQueue
。使用此队列作为阻塞队列时要当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致内存溢出(OOM)。 - 当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列,例如在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。但此时
maximumPoolSize
的值也就无效了。
- 常用的为无界的
-
有界队列:
- 常用的有两类,一类是遵循先进先出(FIFO)原则的队列如
ArrayBlockingQueue
,另一类是优先级队列如PriorityBlockingQueue
。 - 使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低 CPU 使用率和上下文切换,但是可能会限制系统吞吐量。
- 常用的有两类,一类是遵循先进先出(FIFO)原则的队列如
-
同步移交队列:
- 如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用
SynchronousQueue
作为等待队列。 SynchronousQueue
不是一个真正的队列,而是一种线程之间移交的机制。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
- 如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用