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

随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员管理线程并方便地执行并行任务。了解并合理使用线程池,是一个开发人员必修的基本功。

本文开篇简述线程池概念和用途,接着结合线程池的源码,帮助读者领略线程池的设计思路,最后回归实践,通过案例讲述使用线程池遇到的问题,并给出了一种动态化线程池解决方案。

线程池(ThreadPool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。

线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

而本文描述线程池是JDK中提供的ThreadPoolExecutor类。

当然,使用线程池可以带来一系列好处:

线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:

为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。

Poolingisthegroupingtogetherofresources(assets,equipment,personnel,effort,etc.)forthepurposesofmaximizingadvantageorminimizingrisktotheusers.Thetermisusedinfinance,computingandequipmentmanagement.——wikipedia

在计算机领域中的表现为:统一管理IT资源,包括服务器、存储、和网络资源等等。通过共享资源,使用户在低投入中获益。除去线程池,还有其他比较典型的几种使用策略包括:

在了解完“是什么”和“为什么”之后,下面我们来一起深入一下线程池的内部实现原理。

在前文中,我们了解到:线程池是一种通过“池化”思想,帮助我们管理线程而获取并发性的工具,在Java中的体现是ThreadPoolExecutor类。那么它的的详细设计与实现是什么样的呢?我们会在本章进行详细介绍。

Java中的线程池核心实现类是ThreadPoolExecutor,本章基于JDK1.8的源码来分析Java线程池的核心设计与实现。我们首先来看一下ThreadPoolExecutor的UML类图,了解下ThreadPoolExecutor的继承关系。

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

线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3)拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

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

线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量(workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量(workerCount)两个关键参数的维护放在了一起,如下代码所示:

关于内部封装的获取生命周期状态、获取线程池线程数量的计算方法如以下代码所示:

privatestaticintrunStateOf(intc){returnc&~CAPACITY;}//计算当前运行状态privatestaticintworkerCountOf(intc){returnc&CAPACITY;}//计算当前线程数量privatestaticintctlOf(intrs,intwc){returnrs|wc;}//通过状态和线程数生成ctlThreadPoolExecutor的运行状态有5种,分别为:

其生命周期转换如下入所示:

任务调度是线程池的主要入口,当用户提交了一个任务,接下来这个任务将如何执行都是由这个阶段决定的。了解这部分就相当于了解了线程池的核心运行机制。

首先,所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:

其执行流程如下图所示:

任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

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

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

由上文的任务分配部分可知,任务的执行有两种可能:一种是任务直接由新创建的线程执行。另一种是线程从任务队列中获取任务然后执行,执行完任务的空闲线程会再次去从队列中申请任务再去执行。第一种情况仅出现在线程初始创建的时候,第二种是线程获取任务绝大多数的情况。

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

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

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

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

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

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

privatefinalclassWorkerextendsAbstractQueuedSynchronizerimplementsRunnable{finalThreadthread;//Worker持有的线程RunnablefirstTask;//初始化的任务,可以为null}Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。

Worker执行任务的模型如下图所示:

Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。

1.lock方法一旦获取了独占锁,表示当前线程正在执行任务中。2.如果正在执行任务,则不应该中断线程。3.如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。4.线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。

在线程回收过程中就使用到了这种特性,回收过程如下图所示:

增加线程是通过线程池中的addWorker方法,该方法的功能就是增加一个线程,该方法不考虑线程池是在哪个阶段增加的该线程,这个分配线程的策略是在上个步骤完成的,该步骤仅仅完成增加线程,并使它运行,最后返回是否成功这个结果。addWorker方法有两个参数:firstTask、core。firstTask参数用于指定新增的线程执行的第一个任务,该参数可以为空;core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,其执行流程如下图所示:

线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。Worker被创建出来后,就会不断地进行轮询,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。

try{while(task!=null||(task=getTask())!=null){//执行任务}}finally{processWorkerExit(w,completedAbruptly);//获取不到任务时,主动回收自己}线程回收的工作是在processWorkerExit方法完成的。

事实上,在这个方法中,将线程引用移出线程池就已经结束了线程销毁的部分。但由于引起线程销毁的可能性有很多,线程池还要判断是什么引发了这次销毁,是否要改变线程池的现阶段状态,是否要根据新状态,重新分配线程。

在Worker类中的run方法调用了runWorker方法来执行任务,runWorker方法的执行过程如下:

执行流程如下图所示:

在当今的互联网业界,为了最大程度利用CPU的多核性能,并行运算的能力是不可或缺的。通过线程池管理线程获取并发性是一个非常基础的操作,让我们来看两个典型的使用线程池获取并发性的场景。

场景1:快速响应用户请求

场景2:快速处理批量任务

描述:离线的大量计算任务,需要快速执行。比如说,统计某个报表,需要计算出全国各个门店中有哪些商品有某种属性,用于后续营销策略的分析,那么我们需要查询全国所有门店中的所有商品,并且记录具有某属性的商品,然后快速生成报表。

关于线程池配置不合理引发的故障,公司内部有较多记录,下面举一些例子:

Case1:2018年XX页面展示接口大量调用降级:

事故描述:XX页面展示接口产生大量调用降级,数量级在几十到上百。

事故原因:该服务展示接口内部逻辑使用线程池做并行计算,由于没有预估好调用的流量,导致最大核心数设置偏小,大量抛出RejectedExecutionException,触发接口降级条件,示意图如下:

Case2:2018年XX业务服务不可用S2级故障

1.能否不用线程池

回到最初的问题,业务使用线程池是为了获取并发性,对于获取并发性,是否可以有什么其他的方案呢替代?我们尝试进行了一些其他方案的调研:

综合考虑,这些新的方案都能在某种情况下提升并行任务的性能,然而本次重点解决的问题是如何更简易、更安全地获得的并发性。另外,Actor模型的应用实际上甚少,只在Scala中使用广泛,协程框架在Java中维护的也不成熟。这三者现阶段都不是足够的易用,也并不能解决业务上现阶段的问题。

2.追求参数设置合理性?

有没有一种计算公式,能够让开发同学很简易地计算出某种场景中的线程池应该是什么参数呢?

带着这样的疑问,我们调研了业界的一些线程池参数配置方案:

3.线程池参数动态化?

基于以上三个方向对比,我们可以看出参数动态化方向简单有效。

动态化线程池的核心设计包括以下三个方面:

动态化线程池提供如下功能:

参数动态化

JDK原生线程池ThreadPoolExecutor提供了如下几个public的setter方法,如下图所示:

JDK允许线程池使用方通过ThreadPoolExecutor的实例来动态设置线程池的核心策略,以setCorePoolSize为方法例,在运行期线程池使用方调用此方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idel的时候也会被回收;对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务,setCorePoolSize具体流程如下:

线程池内部会处理好当前状态做到平滑修改,其他几个方法限于篇幅,这里不一一介绍。重点是基于这几个public方法,我们只需要维护ThreadPoolExecutor的实例,并且在需要修改的时候拿到实例修改其参数即可。基于以上的思路,我们实现了线程池参数的动态化、线程池参数在管理平台可配置可修改,其效果图如下图所示:

用户可以在管理平台上通过线程池的名字找到指定的线程池,然后对其参数进行修改,保存后会实时生效。目前支持的动态参数包括核心数、最大值、队列长度等。除此之外,在界面中,我们还能看到用户可以配置是否开启告警、队列等待任务告警阈值、活跃度告警等等。关于监控和告警,我们下面一节会对齐进行介绍。

线程池监控

在传统的线程池应用场景中,线程池中的任务执行情况对于用户来说是透明的。比如在一个具体的业务场景中,业务开发申请了一个线程池同时用于执行两种任务,一个是发消息任务、一个是发短信任务,这两类任务实际执行的频率和时长对于用户来说没有一个直观的感受,很可能这两类任务不适合共享一个线程池,但是由于用户无法感知,因此也无从优化。动态化线程池内部实现了任务级别的埋点,且允许为不同的业务任务指定具有业务含义的名称,线程池内部基于这个名称做Transaction打点,基于这个功能,用户可以看到线程池内部任务级别的执行情况,且区分业务,任务监控示意图如下图所示:

用户基于JDK原生线程池ThreadPoolExecutor提供的几个public的getter方法,可以读取到当前线程池的运行状态以及参数,如下图所示:

动态化线程池基于这几个接口封装了运行时状态实时查看的功能,用户基于这个功能可以了解线程池的实时状态,比如当前有多少个工作线程,执行了多少个任务,队列中等待的任务数等等。效果如下图所示:

面对业务中使用线程池遇到的实际问题,我们曾回到支持并发性问题本身来思考有没有取代线程池的方案,也曾尝试着去追求线程池参数设置的合理性,但面对业界方案具体落地的复杂性、可维护性以及真实运行环境的不确定性,我们在前两个方向上可谓“举步维艰”。最终,我们回到线程池参数动态化方向上探索,得出一个且可以解决业务问题的方案,虽然本质上还是没有逃离使用线程池的范畴,但是在成本和收益之间,算是取得了一个很好的平衡。成本在于实现动态化以及监控成本不高,收益在于:在不颠覆原有线程池使用方式的基础之上,从降低线程池参数修改的成本以及多维度监控这两个方面降低了故障发生的概率。希望本文提供的动态化线程池思路能对大家有帮助。

THE END
1.华为申请Qos加速方法及装置专利,提升Qos加速的成功率服务器dns专利摘要显示,本申请公开了一种 Qos 加速方法及装置,应用于终端,Qos 加速方法包括:若终端满足 Qos 加速条件,终端向网络侧设备发送 DNS A 查询、DNS AAAA 查询及 Qos 加速类型查询,Qos 加速类型查询用于查询网络侧设备支持的 Qos 加速类型;接收网络侧设备发送的 DNS A 查询对应的 DNS A 响应、DNS AAAA 查询对应https://www.163.com/dy/article/JFBJKURS0519QIKK.html
2.快速响应机制:客服系统如何提供即时反馈?客户行为分析:通过分析客户行为数据,预测客户需求,提前准备解决方案。 服务绩效评估:定期评估客服表现,根据反馈优化服务流程。 三、快速响应机制的实施步骤 需求分析:明确客服系统需要解决的具体问题和目标。 系统选择与配置:根据需求选择合适的客服系统,并进行必要的配置。 https://www.bangwo8.com/industry_analysis-8736.html
3.收藏~超全Linux应急响应技巧Linux环境下处理应急响应事件往往会更加的棘手,因为相比于Windows,Linux没有像Autorun、procexp这样的应急响应利器,也没有统一的应急响应处理流程。所以,这篇文章将会对Linux环境下的应急响应流程进行讲解,并且提供每一个环节中所用到的shell命令,以帮助大家快速、系统化地处理Linux环境下的病毒。 https://zhuanlan.zhihu.com/p/553963348
4.新门内部资料精准大全,高速响应解决方案规划界面版24.535某大型银行通过新门内部资料精准大全,实现了内部文件的高效管理,同时通过高速响应解决方案规划界面,快速响应市场变化,提高了客户服务质量。 医疗行业应用 一家医院利用新门内部资料精准大全管理病历资料,通过智能化检索系统,医生能够快速找到所需的病历信息,提高了工作效率和医疗服务质量。 http://m.jyqd1314.com/post/6189.html
5.四管齐下共建发展七巧低代码助力零售行业打造一体化协同解决系统自动根据问题的类型、等级、来源等数据进行问题的分类和分配,实现问题的快速响应和解决。 同时,系统自动根据问题的处理过程和结果生成问题的报表和文档,实现问题的可视化和数据化,提高问题的管控能力和效果,终端管理更加规范、高效和有序,终端服务变得更加及时、贴心和优质。 https://qiqiao.do1.com.cn/ext/xueyuan/23780.html
6.实施快速响应的六大步骤实施快速响应的第三步是建立监测和报告机制,以跟踪响应过程和结果。这包括监测响应时间、问题解决率等关键指标,并及时向相关利益相关者报告。通过监测和报告,可以发现和解决问题,并对响应流程进行持续改进和优化。 4. 为了能够做出快速响应,提前准备资源是至关重要的。这包括确保团队具备必要的知识和技能,以及提供必要的https://wenku.baidu.com/view/7bc7c280740bf78a6529647d27284b73f342360e.html
7.节假日期间发生景区舆情怎么处理舆情应对面对突发的景区舆情,快速响应是控制事态发展的关键。景区管理部门应建立一套快速响应机制,包括信息收集、分析、决策和执行等环节。一旦发现负面舆情,应迅速启动应急预案,采取有效措施,如发布官方声明、澄清事实、采取措施解决问题等。 3.正面信息传播 在处理景区舆情的同时,景区还应加强正面信息的传播。通过官方渠道发布景https://www.civiw.com/opinion/20240430161504376
8.仓库管理系统有哪些优势零代码企业数字化知识站快速响应与处理 仓库管理系统可以在订单生成后,立即进行拣货和包装操作,大大缩短了订单的处理时间。客户在下单后能快速收到商品,提升了对企业的信任感和满意度。 交付准时率 通过实时库存更新和精准的物流安排,WMS能够保证商品能在承诺的时间内准时交付到客户手中。这不仅增加了客户的满意度,也提升了企业的信誉。 https://www.jiandaoyun.com/blog/article/109125/
9.第三代大屏幕拼接(非IP光纤拼控)处理器产品说明书指挥中心操作人员通过平板电脑即可完成大屏拼控管理的重要功能,在平板电脑上实时预览所有接入的信号源画面,实现拼接大屏的整屏画面实时切换显示;同时对于需要进行多信号同时显示的应用场景,可视化集控管理平台提供大屏预案一键调用功能,支持保存常用的预案效果,满足应急、公安等行业对于紧急事件及时处理快速响应,一键获取所需https://www.csdn.net/article/2021-08-23/119866076
10.物流运输中的应急管理问题:如何在发生异常情况时快速响应和处理?2.2 快速定位问题:一旦发生异常情况,物流运输公司应通过实时监测系统迅速定位问题源头,例如通过GPS追踪确定车辆所在位置以及是否受到交通拥堵等因素的影响。 2.3 分析问题原因:物流运输公司应对异常情况进行分析,了解问题的具体原因以及可能导致问题的因素,以便采取相应的解决措施。 https://www.daoxu56.com/zixun/881164.html
11.生产异常处理:项目管理系统下的快速响应机制而项目管理系统作为强大的管理工具,为生产异常处理提供了全面的支持。通过实时数据监控、预警机制和快速响应机制,项目管理系统能够帮助企业及时应对生产异常,降低风险,提高生产效率和客户满意度。在未来的发展中,随着项目管理系统的不断演进和完善,相信其在生产异常处理方面的应用将更加广泛和深入。https://www.yopwork.com/yopcms/pcDetail_36798
12.公园安全监测与应急响应解决方案:快速响应紧急情况,提高救援效率5、体积小巧,易于安装部署,快速安装部署,无线网络,免去施工挖土挖沟与复杂布线,节省大量时间,降低施工周期,提升工作效率。 以上就是关于公园安全监测系统解决方案的全部内容了,如果想要了解更多物联网行业相关知识的话,可以点击新闻资讯进行查看。http://www.doing-tech.com/newsinfo/6583300.html
13.应急管理平台应急管理平台可以为应急管理部门提供实时数据和决策支持,帮助应急管理部门快速做出决策和指挥行动。 4.应急演练和培训 应急管理平台还可以进行应急演练和培训,提高应急管理部门的应急处理能力和应变能力。 应急管理平台的优势 应急管理平台具有以下优势: 1.快速响应 https://www.kelicloud.cn/edu/gongyeapp/1742.html
14.售后维修管理软件:快速完善的售后服务工单响应机制售后维修管理软件,顾名思义,是专为解决售后服务流程中维修管理难题而设计的信息化工具。它通过集成客户信息管理、工单创建与分配、维修进度跟踪、备件库存管理、服务报告生成及客户满意度调查等功能模块,实现了售后服务流程的全面数字化、标准化和自动化。其核心价值在于提高服务响应速度、降低运营成本、增强团队协作效率、http://www.shouhouyi.com/contents/4/1939.html
15.提升企业服务效率:飞书工单管理系统解析服务质量的提升不仅依赖于快速响应,还需要工单管理系统能够提供高效的协同工作能力和准确的信息同步功能。在多部门、多角色协作的服务管理过程中,如何确保信息的准确传递和实时更新,是提高服务质量的另一个重要方面。缺乏有效的协同和信息同步机制,将直接影响工单处理的效率和质量。 https://www.feishu.cn/content/svc-work-order
16.QMS系统如何助您快速响应质量问题太友QMS系统在当今竞争激烈的市场中,快速响应质量问题是企业保持竞争力的关键。太友QMS系统提供了一系列工具和解决方案,帮助企业和组织及时捕捉、分析并解决质量问题。 1、实时数据监控 太友QMS系统的实时数据监控功能是其核心优势之一。系统通过与生产线上的传感器和检测设备集成,实时收集产品质量数据。这种实时性使用户能够及时发现生https://www.gztaiyou.com/blog/699.html