Java线程池

基本原理

线程池基本常识

线程池(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提供的策略:

java提供的几种拒绝策略

线程管理

worker线程

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线程退出

worker线程执行任务

runWorker方法执行任务,执行过程:

    1.while循环不断通过getTask()获取任务

    2.getTask()方法从阻塞队列中取任务。

    3.如果线程池正在停止,则保证当前线程是中断状态,否则要保证当前线程不是中断状态。

    4.执行任务。

    5.如果getTask()为null则跳出循环,销毁线程。

java提供的几种线程池

java提供的几种线程池

相关文章:

Java线程池实现原理及其在美团业务中的实践

深入理解Java线程池:ThreadPoolExecutor

jdk1.8 源码

推荐阅读更多精彩内容