【第十一篇】Java线程池ThreadPoolExecutor使用详解【重点】
1.1 概述
引入线程池的原因
1、减少开销提升效率
减少线程的创建和销毁所花的时间以及系统资源的开销;
同时,提高系统响应速度,当有新任务到达时,通过复用已存在的线程,无需等待新线程的创建便可立即执行。
2、提高线程的可管理性
方便管控线程并发数量。线程无限制的创建,可能会导致内存占用过多,从而产生OOM,并且会造成CPU过度切换,CPU切换线程是有时间成本的:需要保持当前执行线程的现场,并恢复要执行线程的现场。
对线程进行统一的分配、调优和监控,从而也提高响应速度;提供更强大的功能,延时定时线程池。
ExecutorService(ThreadPoolExecutor的顶层接口)使用线程池中的线程执行每个提交的任务,通常我们使用Executors的工厂方法来创建ExecutorService。
Executors类的底层实现便是ThreadPoolExecutor! Executors 工厂方法有:
Executors.newCachedThreadPool():无界线程池,可以进行自动线程回收
Executors.newFixedThreadPool(int):固定大小线程池
Executors.newSingleThreadExecutor():单个后台线程
它们均为大多数使用场景预定义了设置。不过在阿里java开发规范文档中说明,尽量不要用该类创建线程池。
1.2 ThreadPoolExecutor类讲解
1.2.1 线程池状态:
五种状态:
线程池的状态 | 说明 |
---|---|
RUNNING | 允许提交并处理任务 |
SHUTDOWN | 不允许提交新的任务,但是会处理完已提交的任务 |
STOP | 不允许提交新的任务,也不会处理阻塞队列中未执行的任务,并设置正在执行的线程的中断标志位 |
TIDYING | 所有任务执行完毕,池中工作的线程数为0,等待执行terminated()勾子方法 |
TERMINATED | terminated()勾子方法执行完毕 |
- 线程池的shutdown() 方法,将线程池由 RUNNING(运行状态)转换为 SHUTDOWN状态
- 线程池的shutdownNow()方法,将线程池由RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。
注:SHUTDOWN 状态 和 STOP 状态 先会转变为 TIDYING 状态,最终都会变为 TERMINATED
1.2.2 ThreadPoolExecutor构造函数
ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
1.2.3 线程池工作原理
1.2.3.1 corePoolSize 、maximumPoolSize 、workQueue
- corePoolSize :线程池中核心线程数的最大值
- maximumPoolSize :线程池中能拥有最多线程数
- workQueue:用于缓存任务的阻塞队列
当调用线程池execute() 方法添加一个任务时,线程池会做如下判断:
1、如果有空闲线程,则直接执行该任务;
2、如果没有空闲线程,且当前运行的线程数少于corePoolSize,则创建新的线程执行该任务;
3、如果没有空闲线程,且当前的线程数等于corePoolSize,同时阻塞队列未满,则将任务入队列,而不添加新的线程;
4、如果没有空闲线程,且阻塞队列已满,同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务;
5、如果没有空闲线程,且阻塞队列已满,同时池中的线程数等于maximumPoolSize ,则根据构造函数中的 handler 指定的策略来拒绝新的任务。
1.2.3.2 KeepAliveTime:
- keepAliveTime :表示空闲线程的存活时间
- TimeUnit unit :表示keepAliveTime的单位
当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
注:如果线程池设置了allowCoreThreadTimeout参数为true(默认false),那么当空闲线程超过keepaliveTime后直接停掉。(不会判断线程数是否大于corePoolSize)即:最终线程数会变为0。
1.2.3.3 workQueue 任务队列:
- workQueue :它决定了缓存任务的排队策略
ThreadPoolExecutor线程池推荐了三种等待队列,它们是:SynchronousQueue 、LinkedBlockingQueue 和 ArrayBlockingQueue。
1.2.3.3.1 有界队列:
- SynchronousQueue :一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
- ArrayBlockingQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
1.2.3.3.2 无界队列:
- LinkedBlockingQueue:基于链表结构的无界阻塞队列,它可以指定容量也可以不指定容量(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)
- PriorityBlockingQueue:是一个按照优先级进行内部元素排序的无界阻塞队列。队列中的元素必须实现 Comparable 接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue 不会保证优先级一样的元素的排序。
注意:keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。
1.2.3.4 threadFactory
- threadFactory :指定创建线程的工厂。(可以不指定)
如果不指定线程工厂时,ThreadPoolExecutor 会使用ThreadPoolExecutor.defaultThreadFactory 创建线程。默认工厂创建的线程:同属于相同的线程组,具有同为 Thread.NORM_PRIORITY 的优先级,以及名为 “pool-XXX-thread-” 的线程名(XXX为创建线程时顺序序号),且创建的线程都是非守护进程。
1.2.3.5 handler 拒绝策略:
- handler :表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略。(可以不指定)
策略 | 解释 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 抛出RejectedExecutionException异常。默认策略 |
ThreadPoolExecutor.CallerRunsPolicy() | 由向线程池提交任务的线程来执行该任务 |
ThreadPoolExecutor.DiscardPolicy() | 抛弃当前的任务 |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃最旧的任务(最先提交而没有得到执行的任务) |
最科学的的还是 AbortPolicy 提供的处理方式:抛出异常,由开发人员进行处理。
1.2.4 常用方法:
除了在创建线程池时指定上述参数的值外,还可在线程池创建以后通过如下方法进行设置。
此外,还有一些方法:
getCorePoolSize():返回线程池的核心线程数,这个值是一直不变的,返回在构造函数中设置的coreSize大小;
getMaximumPoolSize():返回线程池的最大线程数,这个值是一直不变的,返回在构造函数中设置的coreSize大小;
getLargestPoolSize():记录了曾经出现的最大线程个数(水位线);
getPoolSize():线程池中当前线程的数量;
getActiveCount():Returns the approximate(近似) number of threads that are actively executing tasks;
prestartAllCoreThreads():会启动所有核心线程,无论是否有待执行的任务,线程池都会创建新的线程,直到池中线程数量达到 corePoolSize;
prestartCoreThread():会启动一个核心线程(同上);
allowCoreThreadTimeOut(true):允许核心线程在KeepAliveTime时间后,退出;
1.2.5 如何确定线程池中线程数量
IO密集型=常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等
计算密集型=常出现于线程中:复杂算法