Java线程池

作者: 一只咸鱼coding | 来源:发表于2021-02-09 15:53 被阅读0次

    基本原理

    线程池基本常识

    线程池(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 源码

    相关文章

      网友评论

        本文标题:Java线程池

        本文链接:https://www.haomeiwen.com/subject/kfhcxltx.html