一、前言
线程池是一种用于管理和复用线程的机制,它可以有效地控制并发线程的数量,并提供了一种线程复用的机制,减少了线程创建和销毁的开销。实际项目中,常会使用到线程池,要使用线程池,配置好参数十分关键,掌握配置参数对如何用好线程池起了至关重要的作用。
二、线程池常用参数 ThreadPoolExecutor
1.corePoolSize:核心线程数
线程池中一直保持存活的线程数量,即使它们处于空闲状态。
如果线程池中的线程数小于核心线程数,新的任务将创建新线程来处理,即使有核心线程处于空闲状态。
这是为了更快地响应新任务而采取的策略,确保核心线程一直保持存活状态。
在配置corePoolSize之前,需要先了解什么是CPU密集型任务和I/O密集型任务
CPU密集型任务:需要大量使用CPU计算资源的任务。这些任务通常需要进行复杂的数值计算、图像处理、模拟仿真等操作,对CPU的计算能力有很高的要求。相对于I/O密集型任务,CPU密集型任务更加依赖CPU的处理能力,而不是I/O速度。
I/O密集型任务:需要大量使用I/O(输入/输出)操作的任务。这些任务通常需要频繁地读写磁盘、网络或其他外部设备,如文件处理,数据库操作,网络传输等,这些操作对I/O速度有很高的要求。相对于CPU密集型任务,I/O密集型任务更加依赖I/O速度,而不是CPU计算能力。
参数配置:
- CPU密集型:corePoolSize = CPU核数 + 1
- IO密集型:corePoolSize = CPU核数 * 2
2.maximumPoolSize:最大线程数
线程池所允许的最大线程个数
当maxPoolSize>当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务。
当线程数=maxPoolSize,且任务队列已满时,线程池会根据handle策略处理,默认是AbortPolicy 丢弃任务,抛运行时异常。
maxPoolSize往往设置成corePoolSize一样,这样可以减少在处理过程中创建线程的开销。
3.keepAliveTime:线程存活时间
当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0
4.workQueue工作队列
一般选择建议选择有界队列,因为如果任务特别多,核心线程处理不过来,会将任务都放到工作队列中,此时最大线程数已经没有意义了。如果控制不好会导致OOM。
LikendBlockingQueue需要维护一个个Node对象,需要额外的内存消耗。并且在生产和消费的时候,需要创建Node对象进行插入或移除,大批量数据的系统中,其对于GC的压力会比较大。
ArrayBlockingQueue只是维护final Object[] items;一个数组。在生产和消费的时候,是按照索引对数据插入或移除的,不会产生或销毁任何额外的对象实例。
综合来说,可以选择ArrayBlockingQueue
5.queueCapacity:队列容量
当核心线程数达到最大时,新任务会放在队列中排队等待执行。
taskcost:任务耗时
responsetime:相应时间
queueCapacity = (coreSizePool/taskcost)*responsetime
6.rejectedExecutionHandler:拒绝策略
两种情况会拒绝处理任务:
当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常。
JDK提供了四种拒绝策略:
AbortPolicy:直接丢弃新任务,抛出异常
DiscardPolicy:直接丢弃掉,不会抛出异常
DiscardOldestPolicy:丢弃时间最久的任务。一般是队列最前面的任务
CallerRunsPolicy:交给主线程去执行