一、核心线程数(corePoolSize
):
线程池中的基本线程数量
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut
。这里的最小线程数量即是corePoolSize
。
二、最大线程数(maximumPoolSize
):
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize
指定。
三、空闲线程存活时间(keepAliveTime
):
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize
,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime
来设定
四、空闲线程存活时间单位(unit
)
keepAliveTime
的计量单位,也就是空闲线程的存活时间单位
五、工作队列(workQueue
):
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。JDK
中提供了四种工作队列:
①ArrayBlockingQueue
数组型阻塞队列,基于数组的有界阻塞队列,按FIFO
(先进先出策略)排序。
使用一个重入锁(ReentrantLock
),默认使用非公平锁,入队和出队共用一个锁,互斥。
新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize
后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize
,则会执行拒绝策略。
②LinkedBlockingQuene
链表型阻塞队列,基于链表的有界(近似无界)阻塞队列,默认初始化大小为Integer.MAX_VALUE
(其实最大容量为Interger.MAX
),按照FIFO
先进先出策略)排序。
使用一个重入锁(ReentrantLock
),默认使用非公平锁,入队和出队共用一个锁,互斥。
由于该队列的近似无界性,当线程池中线程数量达到corePoolSize
后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize
,因此使用该工作队列时,参数maxPoolSize
其实是不起作用的。
③SynchronousQuene
同步移交队列。容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize
,则执行拒绝策略。
④PriorityBlockingQueue
优先级阻塞队列,具有优先级的无界阻塞队列,优先级通过参数Comparator
实现。
在 put
的时候会tryGrow
,要说它有界也没问题,因为界是 Integer.MAX_VALUE
,但其实上这个队列应该是无界的。默认采用元素自然顺序升序排列(可以自定义Comparator
)。
使用一个重入锁分别控制元素的入队和出队。
⑤DelayQueue
延时队列。无界,队列中的元素有过期时间,过期的元素才能被取出。使用一个重入锁分别控制元素的入队和出队,用 Condition
进行线程间的唤醒和等待。任务调度时候可以使用。
六、线程工厂(threadFactory
):
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon
线程等等
七、任务拒绝策略(handler
):
当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现 RejectedExecutionHandler
接口,有四种策略(1)抛异常、(2)丢弃任务不抛异常、(3)打回任务、(4)尝试与最老的线程竞争。
默认拒绝策略是
AbortPolicy
-
AbortPolicy
:丢弃任务并抛出RejectedExecutionException
异常 -
DiscardPolicy
:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态 -
DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新提交被拒绝的任务 -
CallerRunsPolicy
:由调用线程处理该任务
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run
方法,除非线程池已经shutdown
,则直接抛弃任务。
功能:当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。
使用场景:一般在不允许失败的、对性能要求不高、并发量较小的场景下使用,因为线程池一般情况下不会关闭,也就是提交的任务一定会被运行,但是由于是调用者线程自己执行的,当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。
②AbortPolicy(线程池默认策略)
该策略下,直接丢弃任务,并抛出RejectedExecutionException
异常。
功能:当触发拒绝策略时,直接抛出拒绝执行的异常,中止策略的意思也就是打断当前执行流程
使用场景:这个就没有特殊的场景了,但是一点要正确处理抛出的异常。ThreadPoolExecutor
中默认的策略就是AbortPolicy
,ExecutorService
接口的系列ThreadPoolExecutor
因为都没有显示的设置拒绝策略,所以默认的都是这个。但是请注意,ExecutorService
中的线程池实例队列都是无界的,也就是说把内存撑爆了都不会触发拒绝策略。当自己自定义线程池实例时,使用这个策略一定要处理好触发策略时抛的异常,因为他会打断当前的执行流程。
③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
功能:直接静悄悄的丢弃这个任务,不触发任何动作
使用场景:如果你提交的任务无关紧要,你就可以使用它 。因为它就是个空实现,会悄无声息的吞噬你的的任务。所以这个策略基本上不用了
④DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
功能:如果线程池未关闭,就弹出队列头部的元素,然后尝试执行
使用场景:这个策略还是会丢弃任务,丢弃时也是毫无声息,但是特点是丢弃的是老的未执行的任务,而且是待执行优先级较高的任务。基于这个特性,我能想到的场景就是,发布消息,和修改消息,当消息发布出去后,还未执行,此时更新的消息又来了,这个时候未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了。因为队列中还有可能存在消息版本更低的消息会排队执行,所以在真正处理消息的时候一定要做好消息的版本比较。