ThreadPoolExecutor提供了四种构造函数,但本质都是调用最后一种
这里一共有五种参数(long和TimeUnit表示的是KeepAliveTime,后面我直接将这两个参数表示成KeepAliveTime)分别是corePoolSize、maxPoolSize、keepAliveTime、workQueue、threadFactory、handler。
我们先来了解一下这些参数的大概含义:
参数名 | 类型 | 含义 |
corePoolSize | int | 核心线程数 |
maxPoolSize | int | 最大线程数 |
keepAliveTime | long | 保持存活时间 |
workQueue | BlockingQueue | 任务存储队列 |
threadFactory | ThreadFactory | 当线程池需要新的线程的时候,会使用threadFactory来创建新的线程 |
Handler | RejectedExecutionHandler | 由于线程无法接收你所提交的任务的拒绝策略 |
下面具体讲解一下各个参数
corePoolSize、maxPoolSize、workQueue
corePoolSize
:corePoolSize指的是核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时再创建新线程去执行任务。
maxPoolSize
:线程池有可能在核心线程的基础上,额外增加一些线程,但是增加的线程数是有上限的,这个最大量就是maxPoolSize,也就是说该线程池中最多有maxPoolSize个线程
workQueue
:当核心线程全部在执行任务,但与此同时还有更多的任务进来,那就会放到workQueue队列中
添加线程的规则
说道这里就有必要说一下线程池添加线程的规则了:
提交的任务进来后,会先检查核心线程池是否已满,如果满了就添加到阻塞队列中,如果队列也满了,那么就会开始创建非核心线程执行任务,如果非核心线程的数量达到maxPoolSize,就开始执行拒绝策略。
上面说的这个流程只是比较正常的流程,核心线程满了之后,就一定会存储到阻塞队列中吗,这是不一定的,workQueue有一下三种类型:
1)直接交换:SynchronousQueue,该队列是无法存储任务的,多于corePoolSize的任务会直接开始创建新的非核心线程,直到等于maxPoolSize,这种情况下需要设置maxPoolSize大一些
2)无界队列:LinkedBlockingQueue,设置为这种队列那么maxPoolSize是无效的,因为LinkedBlockingQueue理论上是无上限的,可以应对突发流量,但是有风险,但核心线程的处理速度跟不上任务的提交速度,那么队列中的内容就会越来越多,最后可能会发生OOM
3)有界队列:ArrayBlockingQueue
我们可以根据业务的需要自定义参数构造出最符合我们业务场景的线程池。线程中线程的增减有一下特点:
- 通过设置corePoolSize和maxPoolSize相同,就可以创建固定大小的线程池
- 线程池希望保持较少的线程数,并且只有在负载变得很大的时候才增加它
- 通过设置maxPoolSize为很高的值,例如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务。
- 只有在队列填满后才创建多于corePoolSize数量的线程,所以如果使用的是无界队列(LinkedQueue),那么线程数就不会超过corePoolSize
keepAliveTime
keepAliveTime
主要用来回收超过corePoolSize的线程。
如果线程池当前的线多于corePoolSize
,那么多于的线程的空余时间超过keepAliveTime后,他们就会被终止。
默认情况下是不会回收核心线程的,如果想要回收核心线程,那么需要设置allowCoreThreadTimeOut
为tru
e,那么keepAliveTime
会对核心线程生效。但是我们一般不推荐这样使用。
ThreadFactory
用来创建线程
新的线程是由ThreadFactory
创建的,默认使用Executors.defaultThreadFactory()
,创建出来的线程都在同一个线程组,拥有同样的NORM_PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory
,那么就可以改变线程名、线程组、优先级、是否是守护线程等。
通常使用DefaultThreadFactory就够用了。
RejectedExecutionHandler
线程池所使用的拒绝策略,那么什么时候线程池会采用拒绝策略呢?一般有以下两种时机:
- 当Execuor关闭时,提交新的任务会被拒绝
- 当Executor对最大线程数和工作队列容量使用有限边界并且已经饱和时。
注意:使用LinkedBlockingQueue这种无界队列,线程池在系统能够承受的范围内都不会拒绝新的任务的提交。也就不会在第二种情况下使用拒绝策略
线程池有一下4种拒绝策略:
1、AbortPolicy(默认)
直接抛出异常,java.util.concurrent.RejectedExecutionException
2、DiscardPolicy
直接默默将新来的任务丢弃
3、DiscardOldestPolicy
丢弃队列中存在时间最久的任务,也就是队列头部的任务
4、CallerRunsPolicy(最佳)
让提交任务的线程去执行,这样有两点好处:
- 任务不会被丢弃,避免了业务损失