- 核心和最大线程大小
ThreadPoolExecutor会根据CorePoolSize和maximumPoolSize来自动调整线程池大小。当一个新任务到来且线程池的中线程数量小于corePoolSize且其他工作线程空闲时,ThreadPoolExecutor会创建一个新线程处理这个请求。
ThreadPoolExecutor只会在任务队列已经满了并且线程数量大于corePoolSize但是小于maximumPoolSize时才会新建线程处理新的请求。
通过将corePoolSize和maximumPoolSize可以得到一个固定线程大小的线程池。
通过将maximumPoolSize设置为一个很大的数字,比如:Integer.MAX_VALUE,并且把这种设置放到生产环境上,那么你可能晚上睡不着觉。
一般情况下,线程池的core和maximum大小是通过ThreadPoolExecutor的构造函数来设置的,但是也可以通过setCorePoolSize(int) 和setMaximumPoolSize来更改。
- 按需新建线程
新建ThreadPoolExecutor后,它并不会直接按照corePoolSize的数量新建线程,而是当提交任务后才会新建线程。
但是我们可以通过调用prestartCoreThread()或者prestartAllCoreThreads来启动线程。
只有你创建了一个有任务的队列时,你才可能想要预启动线程。
- 创建线程方式
线程池中创建线程会使用ThreadFactory。如果你在创建线程池时没有指定ThreadFactory,那么ThreadPoolExecturo会使用Executors.defaultThreadFactory()来创建普通优先级且非守护线程。
通过使用不通的ThreadFactory,你可以修改线程的名称,线程组,优先级和是否为守护进程。
如果ThreadFactory在创建线程时返回了一个null对象,那么ThreadPoolExecutor会继续执行,但是并不会执行任务。
- 保活时间
如果线程池当前的线程数量大于corePoolSize,并且这些大于corePoolSize的线程的空闲时间已经超过了keepAliveTime设置的时间,那么这些线程会被终止。
在线程池没有被使用时,这种方式可以减少对资源的消耗。
如果过一段时间线程池又被激活了,那么线程池会再次创建新的线程。
keeAliveTime可以通过调用setKeepAliveTime来动态修改。如果keepAliveTime使用Long.MAX_VALUE就意味着你不想终止空闲的线程。
默认情况下,保活策略只有当线程池的线程数量大于corePoolSize时才会生效,但是你可以通过调用allowCoreThreadTimeOut函数针对核心线程执行超时策略。
- 任务队列
只要是实现了BlockingQueue接口的队列都可以作为任务队列参数传递给线程池。线程池对于队列大小的使用:
- 如果当前正在运行的线程大小小于corePoolSize,那么线程池会一直添加新的线程
- 如果当前线程池线程大小达到或超过了corePoolSize的话,那么线程池会先将任务入队,而不是创建一个新的线程
- 如果一个新任务无法入队,如果线程池大小没有超过了maximumPoolSize的话,那么线程池会创建一个新线程,否则新任务会直接执行拒绝策略
队列的常用策略:
- 直接传递
提交新任务后,新任务需要立即执行。在这种场景下,你需要设置一个无上限的MaximumPoolSize。
- 无界队列
使用一个无界的队列(例如:LinkedBlockingQueue)在核心线程都在忙的时候任务一直等待。因为使用无界队列时,新任务可以一直入队,那么线程池中处于运行状态的线程就不会超过corePoolSize设置的大小,这也就意味着在创建线程池时指定的的MaximumPoolSize不会起作用。无界队列可以用于web Server。
- 有界队列
有界队列可以有效的防止资源耗尽,但是会有点难以调整和控制。需要权衡队列大小和最大线程大小:如果使用大队列和小的线程大小,那么会有减少使用CPU利用率、操作系统资源和上下文切换的开销,但是会导致吞吐量降低;如果任务频繁阻塞(比如:任务是IO密集型的),那么系统的调度时间可能多于任务的执行时间。;如果使用小队列和大的线程池,那么会有很多CPU的调度开销,这当然也会降低系统的吞吐量
- 任务拒绝策略
拒绝策略发生条件:
- 线程池已经终止
- 线程池运行线程已经达到maximum指定的大小且队列已满
预定义的的拒绝策略:
- 默认拒绝策略:AbortPolicy
该拒绝策略是直接抛出一个运行时异常RejectedExecutionException。
- CallerRunsPolicy
该拒绝策略是提交任务的线程执行该任务。
- DiscardPolicy
该拒绝策略是将任务丢弃。
- DiscardOldPolicy
该拒绝策略是将任务队列中第一个任务丢弃,并尝试将该任务重新入队。
自定义策略
- 自定义拒绝策略只需要实现RejectedExecutionHandler接口实现void rejectedExecution(Runnable r,
ThreadPoolExecutor executor);这个函数即可。
- 回调函数
ThreadPoolExecutor提供了beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable)方法,表示在每个任务执行前和每个任务执行后执行该函数。例如:可以重新初始化ThreadLocal线程局部变量,统计数据或添加日志。另外,terminated()函数可以被重新来执行在线程池完全终止后的一些特殊业务处理。
如果钩子后者回调方法抛出了异常,那么可能内部的工作线程可能已经失败了或已经终止了。
- 队列维护
线程池提供了getQueue()允许用户来监控和调试。基于其他目的调用getQueue()并不鼓励。ThreadPoolExectutor提供了两个方法:remove(Runnable)和purge()可以用于在大量任务被取消时协助回收存储。
- 线程池终止
如果线程池没有任务执行并且已经没有了驻留的线程时,那么线程池就自动停止了。基于这个特性,那么你想要保证最终未使用的线程池回收或者用户忘记了调用shutdown()函数,那么你可以通过设置合适的保活时间和0个核心线程或通过设置allowCoreThreadTimeOut(boolean),你可以将未使用的线程死亡。