线程池基本知识

news/2024/5/4 4:08:43/文章来源:https://blog.csdn.net/qq_40634846/article/details/126916935

文章目录

  • 1 问题背景
  • 2 前言
  • 3 线程池是什么
  • 4 线程池核心设计与实现
    • 4.1 总体设计
    • 4.2 运行机制
    • 4.3 生命周期管理
    • 4.4 任务执行机制
      • 4.4.1 任务调度
      • 4.4.2 任务缓冲
      • 4.4.3 任务申请
      • 4.4.4 任务拒绝
    • 4.5 Worker线程管理
      • 4.5.1 Worker线程

1 问题背景

前面总结了工作中的解决方案——电商项目之百万级别的临时订单数据补偿解决方案。其中应用到了线程池,今天简单了解一下线程池的基础知识。

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

2 前言

  • 本篇博客旨在对线程池的各个参数有一个基本的全局了解,基本不涉及案例demo代码。
  • 不必背诵这些知识点,有不懂之处需要上下文反复阅读,反复思考。
  • 本文描述线程池是JDK1.8中提供的ThreadPoolExecutor类。
  • 本文基本都是从Java线程池实现原理及其在美团业务中的实践搬来的,有需要了解更详细的信息可以直接看它。

3 线程池是什么

线程池是池化思想的一种体现。

线程过多的缺点:

  1. 创建、销毁线程的开销
  2. 调度线程的开销

线程池维护多个线程,它解决了

  1. 避免创建、销毁线程的开销
  2. 避免线程数量膨胀导致过分的调度问题

总结:线程池就是解决了线程过多的缺点。可以理解成线程池是一个管理线程的管理者。

线程池的优点:

  1. 提高资源利用率。重复利用已创建的线程。
  2. 提高响应速度。任务到达时,无需等待线程创建即可立刻执行任务。
  3. 提高线程的可管理性。线程是稀缺资源,无限制创建线程会使得线程不合理分布导致降低系统的稳定性。使用线程池进行统一分配、调优和监控。
  4. 提供更多更强大的功能。比如延时、定时线程池,允许任务延时执行或定时执行。

4 线程池核心设计与实现

4.1 总体设计

Java中线程池核心实现类是ThreadPoolExecutor。基于JDK1.8的源码,ThreadPoolExecutor的继承关系如下:

ThreadPoolExecutor的UML类图

  • Executor:该接口的思想是将任务提交和任务执行解耦。用户只需提供Runnable对象(任务的运行逻辑)给Executor,由Executor完成线程的调配和任务的执行。
  • ExecutorService:增加了一些能力:(1)补充了为一个或一批异步任务生成Future的方法;(2)提供了管控线程池的方法。比如停止线程池的运行。
  • AbstractExecutorService:将执行任务的流程串联起来。
  • ThreadPoolExecutor实现最复杂的运行部分。一方面维护自身的生命周期;另一方面同时管理线程和任务,使两者良好结合从而执行并行任务。

4.2 运行机制

ThreadPoolExecutor如何同时维护线程和执行任务的呢?其运行机制如下所示:

ThreadPoolExecutor运行流程
线程池内部使用了生产者消费者模型,将任务和线程解耦。

线程池的运行主要分成两部分:

  • 任务管理: 充当生产者的角色。当任务提交后,线程池会判断该任务后续的流转:(1)直接申请线程执行任务;(2)缓冲到阻塞队列中等待执行;(3)拒绝该任务。
  • 线程管理: 充当消费者的角色。线程被统一维护在图中的线程池内,根据任务请求进行线程分配,线程执行完任务后会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收

接下来,会按照以下三个部分去详细讲解线程池运行机制:

  • 线程池如何维护自身状态。
  • 线程池如何管理任务。
  • 线程池如何管理线程。

4.3 生命周期管理

线程池运行的状态是由其内部维护的。它使用一个变量维护两个值:运行状态(runState)线程数量(workerCount)。源码如下:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl这个变量包含两部分的信息:高3位保存runState,低29位保存workerCount。两部分信息之间互不干扰。

用一个变量存储两个值,可以避免在做相关决策时出现不一致的情况。不必为了维护两者的一致而占用锁资源。

线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数,如下所示:

private static int runStateOf(int c)     { return c & ~CAPACITY; } //计算当前运行状态
private static int workerCountOf(int c)  { return c & CAPACITY; }  //计算当前线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; }   //通过状态和线程数生成ctl

线程 池的五种状态如下:

运行状态状态描述
RUNNING能接收新提交的任务,也能处理阻塞队列中的任务
SHUTDOWN关闭状态,不再接收新提交的任务,但仍会继续处理阻塞队列中的任务
STOP不接收新任务,也不处理阻塞队列中的任务,中断正在处理任务的线程
TYDYING所有的任务已终止,workerCount(有效线程)为0
TERMINATED在terminated() 方法执行完后进入该状态

线程池生命周期的状态转换如下:

线程池生命周期

4.4 任务执行机制

4.4.1 任务调度

任务调度是线程池的主要入口,当用户提交了一个任务,接下来这个任务将如何执行都是由这个阶段决定的。

所有任务调度都是由execute()完成的,其执行过程如下:

  1. 检测线程池的状态,如果不是RUNNING,则直接拒绝。线程池要保证在RUNNING的状态下执行任务。
  2. 如果 workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
  3. 如果 workerCount >= corePoolSize,并且阻塞队列未满,则将任务添加到该阻塞队列中。
  4. 如果 workerCount >= corePoolSize && workerCount < maxmimumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  5. 如果 workerCount >= maxmimumPoolSize,并且阻塞队列已满,则根据拒绝策略来处理任务。默认的方式是直接抛异常。

任务调度流程图如下:

任务调度流程图

4.4.2 任务缓冲

线程池中以生产者消费者模式,通过一个阻塞队列实现,达到将任务与线程解耦的目的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。

下图中展示了线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素:

阻塞队列

使用不同的队列可以实现不一样的任务存取策略。在这里,我们可以再介绍下阻塞队列的成员:

名称描述
ArrayBlockingQueue一个用数组实现的有界队列,此队列按照先进先出(FIFO)原则对元素进行排序。支持公平锁和非公平锁。
LinkedBlockingQueue一个用链表结构组成的有界队列,此队列按照先进先出(FIFO)原则对元素进行排序。队列的默认长度为Integer.MAX_VALUE,所以默认创建的队列有容量危险
PriorityBlockingQueue一个支持线程优先级的无界队列,默认自然序进行排序。也可以实现compareTo()来指定元素排序规则。不能保证同优先级元素的顺序。
DelayQueue一个实现PriorityBlockingQueue实现延时获取的队列。在创建元素时,可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。
SynchronousQueue一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。支持公平锁和非公平锁。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60s后会被回收。
LinkedTransferQueue一个有链表结构组成的无界阻塞队列,相当于其他队列。LinkedTransferQueue多了transfer和tryTransfer方法。
LinkedBlockingDeque一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加移除元素。多线程并发时,可以将锁的竞争最多降到一半。

4.4.3 任务申请

4.4.1任务调度小节中得知,任务的执行有2种可能

  1. 任务直接由新创建的线程执行
  2. 线程从任务队列获取任务然后执行,执行完任务的空闲线程再次从队列中申请任务再去执行。

第一种情况 仅出现在线程初始创建的时候,第二种则是线程获取任务绝大多数的情况。

线程需要从任务缓存模块中不断获取任务执行,帮助线程从阻塞队列中获取任务,实现任务管理模块与线程管理模块之间的通信。这部分策略由getTask()实现,其执行流程如下:

获取任务流程图描述

getTask()进行了多次判断,目的是为了控制线程的数量,使其符合线程池的状态。如果线程池现在不应该持有那么多的线程,则会返回null值。工作线程worker会不断接收新任务执行,当工作线程worker接收不到任务时,就会开始被回收。

4.4.4 任务拒绝

任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。

拒绝策略是一个接口,其设计如下:

public interface RejectedExecutionHandler {void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

用户可以通过实现这个接口去定制拒绝策略,也可以选择JDK提供的四种已有拒绝策略,其特点如下:

序号名称描述
1ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。这是线程池默认的拒绝策略。在任务不能再提交时,抛出异常,及时反馈 程序运行状态。如果是比较关键的业务,推荐使用该策略,这样子在系统不能承受更大的并发量的时候,能够及时通过异常发现。
2ThreadPoolExecutor.DiscardPolicy丢弃任务,但不抛异常。使用该策略会使得我们无法及时发现系统的异常状态,建议是一些无关紧要的业务采用此策略。
3ThreadPoolExecutor.DiscardOldestPolicy丢弃队列最前面的任务,然后重新提交被拒绝的任务。是否采用此种策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
4ThreadPoolExecutor.CallerRunsPolicy由调用线程(提交任务的线程)处理该任务。这种情况是需要所有任务都执行完毕,那么就适合大量计算的任务类型去执行。多线程仅仅是增大吞吐量的手段,最终必须要让每个任务都执行完毕。

4.5 Worker线程管理

4.5.1 Worker线程

线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker。部分代码如下:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{final Thread thread;//Worker持有的线程Runnable firstTask;//初始化的任务,可以为null
}

记录:后续的知识都是围绕线程管理如何运行的,笔者都不太懂,打算不继续写下去了,需要了解详情可以看美团的文章。基本是说申请添加线程,回收线程等,都会有涉及锁的操作。所以创建线程、回收线程会消耗一定的系统资源。文章末尾还给出线程池的一整套解决方案,动态参数化设置线程池的核心参数、监控、告警、权限修改等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_13321.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EEG巨型分析I:跨研究的频谱和振幅特征

导读 通过汇集多项研究的统计结果(元分析)&#xff0c;fMRI领域取得了重大成就。最近&#xff0c;fMRI标准化工作的重点是实现跨研究(巨型分析)的fMRI原始数据的联合分析&#xff0c;以期获得更详细的见解。然而&#xff0c;目前尚不清楚在EEG领域的此类分析是否可能或同样富有…

公众号如何搭建使用查题功能的方法

公众号如何搭建使用查题功能的方法 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&a…

Vue入门【三】-- 详解computed计算属性

目录 computed: ♡ ‧₊˚ 基本使用 ‧₊˚ ♡ ♡ ‧₊˚语法‧₊˚ ♡ ♡ ‧₊˚效果‧₊˚ ♡ ♡ ‧₊˚ 面试问点 ‧₊˚ ♡ computed与methods的区别&#xff1a; computed与watch的区别&#xff1a; computed: 首先我们要知道computed计算属性是什么&#xff0c;在v…

我是一名开发人员,但我不会教我的孩子编程

编程需要的不仅仅是儿童书籍中教授的简单干巴巴的语言。 长按关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 在最近的一次深夜公式跑中&#xff0c;我路过一大堆关于教孩子编程的书籍。我在周围看过这些书&#xff0c;但从来没有这么大的针对小学生…

STM32时钟系统和TIMER配置(溢出中断/PWM)实例

目录&#xff1a; 1. STM32时钟系统 2. STM32的定时器典型配置之溢出中断 3. STM32的定时器典型配置之PWM输出 1. STM32时钟系统 &#xff08;1&#xff09;Clock tree 可以在官方手册(Stm32x-series-Reference-manual)的clock tree中可以对相应MCU的时钟系统有个大致了解…

数字信号处理——多速率信号处理(1)

目录 引言 1、抽取 MATLAB仿真代码 仿真结果 抽取滤波器 2、内插

《关于我摸鱼一天后搞定PyCharm这件事》Python环境配置

Anaconda部署 1.下载最新版匹配电脑的版本 我是 WIN-x64 下载Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 我是 Anaconda3-2022.05-Windows-x86_64 下载 社区版本Pycharm Download PyCharm: Python IDE for Professional Develo…

从零玩转jQuery(基础篇)

1.前言 &#xff08;window.onload和jQuery的相关介绍&#xff09; 分析一下&#xff0c;元素js的缺点: 1.不能添加多个入口函数&#xff08;window.onload),如果添加了多个&#xff0c;后面的会把前面的给覆盖。 2.原生js的api名字都太长太难记。 3.原生js有的时候代码冗余。 …

Maven坐标查找方法及Maven-Search 插件的使用(保姆级教学)

目录坐标的概念获取坐标方法一获取坐标方法二坐标的概念 坐标组成是groupid,artifiactld,version。坐标概念来自数学。 坐标作用:确定资源的&#xff0c;是资源的唯一标识。在maven中&#xff0c;每个资源都有坐标。坐标值是唯一的。简称叫gav。 拿servlet依赖举例 <!--serv…

nacos 简介和使用

Nacos 是阿里巴巴开源项目&#xff0c;用于构建微服务应用的服务发现、配置管理和服务管理。 在微服务项目中不同模块之间服务调用时&#xff0c;实现服务注册与发现。 Nacos 使用&#xff1a; Nacos 是java开发的&#xff0c;依赖 Java 环境来运行 Nacos的下载 https://git…

cobbler部署

cobbler部署 #先关闭防火墙和selinux [root@localhost ~]# systemctl disable firewalld [root@localhost ~]# setenforce 0 //cobbler服务,selinux必须得是disabled状态,所以要重启 [root@localhost ~]# vim /etc/selinux/config SELINUX=disabled [root@localhost ~]# reb…

BUUCTF-社团考核

目录 1.[CISCN2019 华东南赛区]Web4 2.[GWCTF 2019]枯燥的抽奖 3.[NCTF2019]Fake XML cookbook 4.[SCTF2019]Flag Shop 5.[GWCTF 2019]mypassword 6.[BSidesCF 2019]Kookie 7.[WUSTCTF2020]朴实无华 8.[网鼎杯 2020 白虎组]PicDown 9.[CISCN 2019 初赛]Love Math 10.攻…

深入ArrayList()源码

深入ArrayList()源码 jdk1.8 java.util; 扩容机制 新数组都将替代旧数组&#xff0c;旧数组作为垃圾被回收 ArrayList() 会使用长度为零的数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA {};public ArrayList() {this.elementData DEFAULTCAPACITY…

采用 ALSTM 模型的温度和降雨关联预测研究论文学习记录

为了准确和及时预测局部区域的降雨及温度&#xff0c;提出了一种基于 Attention 和 LSTM 组合模型( ALSTM) 的关联多值预测算法。该算法利用天气时间序列中 的前期数据&#xff0c;对下一小时的降雨量和温度进行关联预测&#xff0c;以此实现对天气要素的多值预测。该算法首先对…

一个项目带你走进接口自动化测试

文章目录前言一、项目环境搭建二、项目分析三、框架搭建1、解决登录问题、获取token2、熟悉项目的接口请求方式、二次封装requests请求3、缓解业务请求接口参数臃肿4、重新封装logging日志5、通用方法编写四、编写自动化脚本场景业务需求单接口业务需求五、生成测试报告六、写在…

CSDN线上竞赛编程第六期参赛心得

CSDN编程竞赛报名地址&#xff1a;https://edu.csdn.net/contest/detail/16 &#xff08;请不要删掉此地址&#xff09; CSDN线上竞赛编程第六期参赛心得前言/背景大赛简介参赛流程参赛经历解题思路1、题目名称&#xff1a;严查枪火2、题目名称&#xff1a;鬼画符门3、题目名称…

HDMI/DVI____TMDS编码

一.编码步骤:基本方法:取第一位数据为初值,接下来输入的每一位与前一导出的位(根据判断条件)进行异或XOR或者同或XNOR(最小化传输);最后选择性反转这9bit数据(DC平衡处理)。 ①DE为高电平时,对8位RGB数据编码,第9bit表示采用了XOR / XNOR ,第10bit表示是否翻转。 …

04代码

import datetime #定义一个列表 mot=["今天星期一:\n坚持下去不是因为我很坚强,而是因为我别无选择。","今天星期二:\n含泪播种的人一定能笑着收获。","今天星期三:\n作对的事情比把事情做对更重要。","今天星期四:\n命运给予我们的不是失…

【NLP】自然语言处理的序列建模

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

Teams Bot App 初探

上一篇文章深入讲了incoming webhook。这篇文章我们来看一个稍微复杂点的&#xff0c;正式点的 teams app&#xff1a;bot。 我们先来和之前一样&#xff0c;通过teams toolkit 的 sample gallery来创建一个 Teams bot app。 创建之后我们先来看一下目录结构和生成的文件。 一…