一、前言
在开发过程中,好多场景要用到线程池。每次都是自己根据业务场景来设置线程池中的各个参数。
二、ThreadPoolExecutor的重要参数
我们先来看下ThreadPoolExecutor的带的那些重要参数的构造器。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
1、corePoolSize: 核心线程数
这个应该是最重要的参数了,所以如何合理的设置它十分重要。
1、 核心线程会一直存活,及时没有任务需要执行。
2、 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。
3、 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
如何设置好的前提我们要很清楚的知道CPU密集型和IO密集型的区别。
(1)、CPU密集型
CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading 很高。
在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部分时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
(2)、IO密集型
IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力
(3)、先看下机器的CPU核数,然后在设定具体参数
自己测一下自己机器的核数
System.out.println(Runtime.getRuntime().availableProcessors());
(4)、分析下线程池处理的程序是CPU密集型还是IO密集型
CPU密集型:corePoolSize = CPU核数 + 1
IO密集型:corePoolSize = CPU核数 * 2
2、maximumPoolSize:最大线程数
1、 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务。
2、 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常。
3、keepAliveTime:线程空闲时间
1、 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize。
2、 如果allowCoreThreadTimeout=true,则会直到线程数量=0。
4、queueCapacity:任务队列容量(阻塞队列)
当核心线程数达到最大时,新任务会放在队列中排队等待执行
5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器
两种情况会拒绝处理任务:
1、 当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
2、 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor 采用了策略的设计模式来处理拒绝任务的几种场景。
这几种策略模式都实现了RejectedExecutionHandler 接口。
AbortPolicy 丢弃任务,抛运行时异常。
CallerRunsPolicy 执行任务。
DiscardPolicy 忽视,什么都不会发生。
DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。