基本原理
线程池基本常识
线程池(Thread Pool)是一种基于池化思想管理线程的工具。线程频繁的创建、销毁会产生大量的系统内核调用,消耗CPU资源。用线程池来维护多个线程的生命周期,一方面可以避免线程频繁地创建销毁,另一方面也可以解决线程的调度管理问题。
使用线程池带来一系列好处:
降低资源消耗:池化技术重复利用已创建的线程,从而降低线程创建和销毁造成的损耗。
提高响应速度:任务到达时,如果有空闲线程,则任务无须再等待线程创建。
线程可管理:线程交由线程池统一管理,可以避免线程无限制创建造成的资源损耗,以及一些线程分布不合理造成的资源调度失衡。
更强大的功能:面向开发人员更加灵活强大的操作。如定时执行或者延时执行。
池化思想在很多领域都有广泛应用,其他几种比较典型的使用策略:
1.内存池 2.连接池 3.实例池
线程池核心设计和实现
本文我们主要讨论java的线程池。核心实现类是ThreadPoolExcutor。我们基于jdk1.8论述。
上图是一个简略的类继承图。线程池基本思想是:将任务提交和任务执行解耦。用户只需要提供一个runnable对象,然后将任务交给执行器Executor,无需关注线程如何创建,执行过程。
ExecutorService则为执行器增加了一些能力:
扩充执行任务的能力,补充可以为一个或者一批异步任务生成Future的方法;
提供了控制线程池的方法,例如停止线程池执行。
ExcutorAbstractService则是抽象实现类,将执行任务的流程封装起来,保证下层实现时能够简单且正确。
ThreadPoolExecutor的运行如下:
线程池主要分为两部分:任务管理和线程管理。
任务管理部分主要负责线程的流转:
1.直接申请线程执行任务;
2.放到等待队列等待执行;
3.直接拒绝该任务。
线程管理主要负责线程分配及回收。
线程池自身生命周期
线程池运行的状态,是由线程池内部维护的。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量(workerCount)。
线程池中定义的运行状态有5种:
线程池运行状态转换:
任务管理
任务调度
任务的调度都由execute方法完成,这部分完成的工作包括:检查线程池的运行状态、线程运行数、运行策略,从而决定接下来的流程,是直接申请线程,还是放到缓冲队列,亦或者直接拒绝该任务。执行流程如下:
1.确定线程池是RUNNING状态,否则直接拒绝。
2.正在运行的线程数 < corePoolSize,直接创建并启动一个新线程执行任务。
3.正在运行的线程数 >=corePoolSize,且线程池内阻塞队列未满,则任务放入队列。
4.阻塞队列已满,正在运行的线程>=corePoolSize && 正在运行的线程数 < maximumPoolSize,创建并启动一个线程来执行任务。
线程池阻塞队列已满, 正在运行的线程数 >= maximumPoolSize,根据拒绝策略处理该任务,默认方式是抛出异常。
任务调度流程图:
任务缓冲
线程池本质是对任务和线程的管理,做到这一点最关键的思想就是任务和线程解耦。线程池采用生产者消费者模式,通过阻塞队列实现。阻塞队列缓存任务,工作线程从阻塞队列获取任务。
BlockingQueue(阻塞队列):当队列为空时,消费线程会等待队列非空;当队列满时,生产线程会等待队列可用。
任务申请
当工作线程空闲后,会尝试获取线程,获取过程中会做如下判断:
1.线程池是否已经停止运行,如果是,则返回null
2.线程数现阶段是否过多,如果超出设置数量,会返回null
3.线程如果一直获取不到任务,就会被回收掉,从而保证线程数量处在一个可控范围内。
核心方法如下:
任务拒绝
任务拒绝是线程池的保护策略,当线程池达到最大容量(缓存队列已满且线程数达到设置最大值),会对任务进行拒绝。拒绝策略是一个接口:
public interface RejectedExecutionHandler{
void rejectedExecution(Runnabler,ThreadPoolExecutor executor);
}
用户可以自定义拒绝策略,或者采用jdk提供的策略:
线程管理
worker线程
线程池通过一张Hash表去维护线程的引用,这样可以通过添加引用、移除引用来控制线程的生命周期。
worker通过继承AQS来实现独占锁的功能,使用不可重入锁来控制线程的执行状态。
1.lock方法一旦获取了独占锁,就表示线程正在执行任务。
2.如果正在执行任务,则线程不应该中断。
3.如果线程不是独占锁状态,就说明他是空闲状态,可以中断该线程。
4.线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。
worker线程增加
addWorker方法增加线程,这个方法里面有两个参数:firstTask、core。firstTask用于指定新增的线程执行的第一个任务,该参数可以为空;core=true表示增加线程是判断当前活动线程数是否少于corePoolSize,core=false表示新增线程需要判断当前活动线程数是否少于maximumPoolSize。
worker线程回收
线程回收主要依赖JVM自动回收,线程池做的工作只是维护线程的引用,防止线程被回收,当一些线程需要被回收时,只要删除他的引用即可。Worker被创建出来后,就会不断轮询获取任务执行。当Worker无法获取任务时,就会结束循环,Worker会主动消除自己身上的引用。
worker线程执行任务
runWorker方法执行任务,执行过程:
1.while循环不断通过getTask()获取任务
2.getTask()方法从阻塞队列中取任务。
3.如果线程池正在停止,则保证当前线程是中断状态,否则要保证当前线程不是中断状态。
4.执行任务。
5.如果getTask()为null则跳出循环,销毁线程。
java提供的几种线程池
相关文章:
Java线程池实现原理及其在美团业务中的实践
深入理解Java线程池:ThreadPoolExecutor
jdk1.8 源码