线程池的参数
1)corePoolSize
(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()
方法,线程池会提前创建并启动所有基本线程。
2)runnableTaskQueue
(任务队列):用于保存等待执行的任务的阻塞队列。建议选用有界队列,原因如下:
- 如果是无界队列,那么阻塞队列永远不会满,不会触发到
maximumPoolsize
,意味着maximumPoolsize
这个参数没有作用 - 最重要的是无界队列无法控制队列最终包含的数据量,导致内存资源的极大的消耗甚至耗尽
- 选用有界队列并合理的配置
maximumPoolsize
- 饱和策略的使用根据需求选择。一旦触发了饱和策略,就说明要么是线程池配置存在问题,要么是并发量太高,任务太多导致的问题。及时提醒去做调整
3)maximumPoolsize
(线程池最大数量):线程池允许创建的最大线程数。
4)ThreadFactory
:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的
线程设置更有意义的名字。
5)RejectedExecutionHandler
(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy
,表示无法处理新任务时抛出异常。在
JDK 1.5 中 Java 线程池框架提供了以下 4 种策略。
AbortPolicy
:直接抛出异常。CallerRunsPolicy
:只用调用者所在线程来运行任务。DiscardOldestPolicy
:丢弃队列里最近的一个任务,并执行当前任务。DiscardPolicy
:不处理,丢弃掉。
也可以根据应用场景需要来实现
RejectedExecutionHandler
接口自定义策略(最推荐),
如记录日志或持久化存储不能处理的任务。
6)keepAliveTime
(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
7)TimeUnit
(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
合理的配置线程池
要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
- 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
- 任务的优先级:高、中和低。
- 任务的执行时间:长、中和短。
- 任务的依赖性:是否依赖其他系统资源,如数据库连接。
性质不同的任务可以用不同规模的线程池分开处理。CPU 密集型任务应配置尽可能小的线程,如配置 Ncpu+1 个线程的线程池。由于 IO 密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个 CPU 密集型任务和一个 IO 密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。
核心:
- 明确业务环境,接口的重要性
- 线程池的配置需要基于压测,来评估线程池的参数设置是否合理
- 需要动态配置线程池参数,可以结合配置中心