您当前的位置: 首页 > 

小志的博客

暂无认证

  • 0浏览

    0关注

    1217博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

线程池(ThreadPoolExecutor)源码解析

小志的博客 发布时间:2021-12-15 21:16:47 ,浏览量:0

目录
    • 一、线程池概念介绍
      • 1.1、什么是线程池
      • 1.2、线程池工作流程
      • 1.3、线程池的代码用例
    • 二、源码解析——构造函数
      • 2.1、线程池的构造函数及类的继承关系
      • 2.2、Executors提供的线程池模板
      • 2.3、拒绝策略的实现
      • 2.4、任务队列BlockingQueue的实现
    • 三、 源码解析——execute(Runnable command)
      • 3.1、流程概述
      • 3.2、什么是ctl
      • 3.3、线程池中的线程数量小于核心线程数代码逻辑
      • 3.4、任务添加到队列的代码逻辑
      • 3.5、 线程池中的线程数量小于最大线程数代码逻辑以及拒绝策略的代码逻辑
    • 四、 源码解析——addWorker(Runnable firstTask, boolean core)
      • 4.1、 流程概述
      • 4.2、 retry
      • 4.3、 addWorkder的第一部分解析
      • 4.4、 addWorkder的第二部分解析
    • 五、源码解析—— runWorker(Worker w)
    • 六、源码解析—— getTask()
    • 七、源码解析—— reject(Runnable command)

一、线程池概念介绍 1.1、什么是线程池
  • 池化技术其实在技术生态圈中是比较常见的,比如:对象池、连接池等等。
  • 为什么会有线程池这个东西存在呢?其实原因跟其他池化技术是一样的,都是由于创建对象、连接、线程等操作时是比较“重”的,耗时的,高成本的。针对与这种场景,我们都会采用能复用就复用,而不是每次都要重新的去创建一遍,那么在性能的提升、资源的利用率上,都能获得非常理想的效果。
  • 在介绍线程池之前,我们先插入一个小内容,就是线程状态流转如下图: 在这里插入图片描述
1.2、线程池工作流程
  • 线程池的工作原理大致分为4步,如下图所示: 在这里插入图片描述

  • 【上图解释如下】

  • 首先,当有任务要执行的时候,会计算线程池中存在的线程数量与核心线程数量(corePoolSize)进行比较,如果小于,则在线程池中创建线程,否则,进行下一步判断。

  • 其次,如果不满足上面的条件,则会将任务添加到阻塞队列/font(1.6、 阻塞队列)中。等待线程池中的线程空闲下来后,获取队列中的任务进行执行。

  • 然后,如果队列中也塞满了任务,那么会计算线程池中存在的线程数量与最大线程数量(maxnumPoolSize)进行比较,如果小于,则在线程池中创建线程。

  • 最后,如果上面都不满足,则会执行对应的拒绝策略(1.5、拒绝策略)。

1.3、线程池的代码用例
  • 如上已经了解到了线程池的基本工作流程,那么如何使用线程池呢?如下图实例了解如何使用线程池,才可以基于它对应的方法进行源码分析。 在这里插入图片描述

  • 通过上面的使用例子,我们针对源码的解析就要针对红框的这两点进行深入探索,一个是ThreadPoolExecutor的构造函数,另一个就是execute方法。

二、源码解析——构造函数 2.1、线程池的构造函数及类的继承关系
  • 首先,我们先要了解一下ThreadPoolExecutor线程池类的继承关系,好对它有一个宏观的认知,如下图所示: 在这里插入图片描述 在这里插入图片描述

  • 那么针对它的构造函数,其实我们能够发现,线程池的工作原理在构造函数的入参中都是有映射的,如下所示: 在这里插入图片描述

  • 【上图解释如下】

  • corePoolSize: 核心线程数。

  • maximumPoolSize: 最大线程数。

  • keepAliveTime: 线程池中线程的最大闲置生命周期。

  • unit: 针对keepAliveTime的时间单位。

  • workQueue: 阻塞队列。

  • threadFactory: 创建线程的线程工厂。

  • handler: 拒绝策略。

2.2、Executors提供的线程池模板
  • 在Executors中,已经给我们提供了很多种线程池的实现。如下图所示: 在这里插入图片描述

  • 【上图解释如下】

  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

  • newSingleThreadScheduleExecutor 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程会代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newScheduledThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

2.3、拒绝策略的实现
  • 线程池中提供了如下拒绝策略: 在这里插入图片描述

  • 【上图解释如下】

  • AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。

  • DiscardPolicy 丢弃任务,但是不抛出异常。

  • DiscardOldestPolicy 丢弃队列中最前面的任务,然后重新尝试执行任务。

  • CallerRunsPolicy 由调用线程处理该任务。

2.4、任务队列BlockingQueue的实现
  • 线程池中提供了如下任务队列: 在这里插入图片描述

  • 【上图解释如下】

  • ArrayBlockingQueue 它是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。一但初始化,大小就无法修改

  • LinkedBlockingQueue 它内部以一个链式结构(链接节点)对其元素进行存储。可以指定元素上限,否则,上限则为Integer.MAX_VALUE。

  • DelayQueue 它对元素进行持有直到一个特定的延迟到期。注意:进入其中的元素必须实现Delayed接口。

  • PriorityBlockingQueue 它是一个无界的并发队列。无法向这个队列中插入null值。所有插入到这个队列中的元素必须实现Comparable接口。因此该队列中元素的排序就取决于你自己的Comparable实现。

  • SynchronousQueue 它是一个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有一个元素的话,那么试图向队列中插入一个新元素的线程将会阻塞,直到另一个新线程将该元素从队列中抽走。同样的,如果队列为空,试图向队列中抽取一个元素的线程将会被阻塞,直到另一个线程向队列中插入了一条新的元素。因此,它其实不太像是一个队列,而更像是一个汇合点。

三、 源码解析——execute(Runnable command)
  • 针对execute方法可以分为四部分来分析,下面就针对这四部分深入解析
  • 第一部分:什么是ctl
  • 第二部分:线程池中的线程数量小于核心线程数的代码逻辑
  • 第三部分:不满足【第二部分条件】时,任务添加到队列的代码逻辑
  • 第四部分:不满足【第三部分条件】时,线程池中的线程数量小于最大线程数代码逻辑以及拒绝策略的代码逻辑
3.1、流程概述
  • execute的整体流程如下所示: 在这里插入图片描述

  • 【上图解释如下】

  • 在上面的流程图中,我们看到三块绿色的addWorker方法,和两块红色的reject方法,这两个方法是我们解析的重点。

3.2、什么是ctl
  • 当我们看execute方法的时候,首先看到的就是ctl。 在这里插入图片描述

  • 那么ctl到底是什么东西呢?下面截图源码和注释就是ctl所包含的方法 在这里插入图片描述

  • 针对ctl的值,其实是两部分组成的:【高3位】表示:线程池状态 + 【低29位】表示:线程池中线程数量,如下图所示: 在这里插入图片描述

  • 【上图解释如下】

  • 上图中的runState:-1、0、1、2、3,其实是针对于红色3位来计算的,其实应该是32位来计算,这么写是为了方便大家直观感受到这5种状态值的大小。 其实如果按照32位来计算的哈,从小到大状态的排序依然是:RUNNING

关注
打赏
1661269038
查看更多评论
立即登录/注册

微信扫码登录

0.0527s