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种,分别为:

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

2.3.1任务调度

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

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

其执行流程如下图所示:

2.3.2任务缓冲

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

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

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

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

2.3.3任务申请

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

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

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

2.3.4任务拒绝

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

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

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

2.4Worker线程管理

2.4.1Worker线程

线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程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方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。

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

2.4.2Worker线程增加

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

2.4.3Worker线程回收

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

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

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

2.4.4Worker线程执行任务

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

1.while循环不断地通过getTask()方法获取任务。2.getTask()方法从阻塞队列中取任务。3.如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。4.执行任务。5.如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程。

执行流程如下图所示:

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

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

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

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

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

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

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

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

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

业务中要使用线程池,而使用不当又会导致故障,那么我们怎样才能更好地使用线程池呢?针对这个问题,我们下面延展几个方向:

1.能否不用线程池

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

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

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

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

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

3.线程池参数动态化?

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

3.3.1整体设计

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

3.3.2功能架构

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

参数动态化

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

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

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

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

线程池监控

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

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

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

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

美团到店综合研发中心长期招聘前端、后端、数据仓库、机器学习/数据挖掘算法工程师,欢迎感兴趣的同学发送简历到:tech@meituan.com(邮件标题注明:美团到店综合研发中心-上海)

THE END
1.深度解析数据挖掘业务理解阶段的重要性在于确保数据挖掘项目与业务目标紧密相连,项目的结果能够对业务决策产生实际的影响。通过与业务相关的人员深入交流和了解,数据挖掘团队能够更好地理解业务需求,从而在后续的数据挖掘过程中有针对性地选择合适的数据和技术,提高数据挖掘项目的成功率和效果。 https://zhuanlan.zhihu.com/p/670517974
2.什么是数据挖掘?初学者指南深度学习在图像识别、自然语言处理等领域取得了显著进展,其在数据挖掘中的应用也将越 来越广泛。 3. 实时数据挖掘 随着物联网(IoT)和在线服务的兴起,实时数据挖掘变得越来越重要。 这要求系统能够快速响应并从数据流中提取有价值的信息。 4. 自动化数据挖掘 https://blog.csdn.net/m0_67484548/article/details/142624697
3.数据挖掘指什么意思帆软数字化转型知识库这些工具和软件提供了丰富的数据挖掘功能,可以帮助用户进行数据预处理、数据变换、模式识别、知识表达和评估等。Weka是一款开源的数据挖掘软件,具有丰富的数据挖掘算法和数据预处理工具。RapidMiner是一款功能强大的数据挖掘软件,支持可视化的挖掘流程设计。KNIME是一款基于节点的数据分析平台,可以方便地进行数据挖掘和机器学习https://www.fanruan.com/blog/article/572886/
4.什么是数据挖掘?数据挖掘具有哪些功能?数据挖掘是指从大量数据中提取隐含的、未知的、潜在的有用的信息,使其表现为概念、规则、规律、模式等形式。数据挖掘实质上是一个深层次的数据分析过程,即从大量的数据中,抽取出潜在的、有价值的知识、模型或规则的过程。 ( 1 )趋势和行为分析;( 2 )关联分析;( 3 )聚类分析;( 4 )概念描述;( 5 )偏差检测https://www.shuashuati.com/ti/b42dc41b1dec47e08e65ba734ea56cff.html
5.什么是数据挖掘?数据挖掘有哪些方面的功能?什么是决策树?如何用决策树进行分类? 11212018-12 2 分类知识的发现方法和步骤是什么? 8882018-12 3 什么是聚类?它与分类有什么异同? 9492018-12 4 什么是数据仓库?为什么要建立数据仓库? 10552018-12 5 什么是数据挖掘?数据挖掘有哪些方面的功能? 12242018-12 6 数据仓库技术发展历程 12022018-12 7 回归模型:https://www.ximalaya.com/sound/145864985
6.CDA数据分析练习题2《数据分析专项练习题库》《CDA数据分析认证考试模拟题库》《企业数据分析面试题库》一、单选题1.某超市研究销售纪录数据后发现,买啤酒的人很大概率也会购买尿布,这种属于数据挖掘的哪类问题?(A)A.关联规则发现B.聚类C.分类D.自然语言处理2.以下两种描述分 https://www.cda.cn/bigdata/16318.html
7.数据仓库和数据挖掘12篇(全文)数据仓库和数据挖掘 第1篇 在90年代初, 数据仓库这个概念第一次是由数据仓库支付提出的。一般一个面向集成、主题的, 随着时间变化并且信息本身能够相对稳定的数据集合就成为一个数据仓库, 它主要用于对管理决策过程的支持。 (一) 数据仓库的类型 根据数据仓库所管理的数据类型和它们所解决的企业问题范围, 数据仓库可https://www.99xueshu.com/w/ikeyy9nb2adc.html
8.数据挖掘论文4.DBMiner是由DBMiner Technology公司开发的,它提供多种数据挖掘算法,包括发现驱动的OLAP分析、关联、分类和聚类。特色是它的基于数据立方体的联机分析挖掘,它包含多种有效的频繁模式挖掘功能和集成的可视化分类方法 四、数据挖掘与管理会计 1.提供有力的决策支持 https://www.unjs.com/lunwen/f/20220924130749_5650839.html
9.习题数据挖掘是一项以()为基础的数据分析技术,其主要功能是在习题:数据挖掘是一项以()为基础的数据分析技术,其主要功能是在大量数据中自动发现潜在有用的知识,这些知识可以被表示为概念、规则、规律、模式等。( 本站收集整理了大量习题及答案,请使用站内查询查找 标准答案:答案正在整理中!!! 八字精批2025运势命中贵人八字合婚 姓名 性别 男女 生日https://m.rwtext.com/P7GZ62AH.html
10.sqlserver是什么软件?聚名资讯4. 数据复制和同步:SQL Server支持数据复制和同步功能,可以将数据从一个数据库复制到另一个数据库,实现数据的备份、灾难恢复和分布式部署。 5. 商业智能和分析:SQL Server提供了强大的商业智能和分析功能,包括数据仓库、数据挖掘、报表和分析服务等。它能够处理大量的数据,并提供高级的数据分析和决策支持功能。 https://www.juming.com/zx/20355.html
11.工厂crm软件哪个好零代码企业数字化知识站未来,更多的工厂CRM软件将注重智能化和数据分析功能,通过人工智能和大数据技术,提供更加精准和高效的业务管理解决方案。简道云在这方面已经走在了前列,其智能分析模块和数据挖掘功能,使用户能够深入分析业务数据,发现潜在问题和机会,优化业务流程,提升整体竞争力。https://www.jiandaoyun.com/blog/article/789643/
12.数据挖掘数据挖掘面试题汇总测测你的专业能力是否过关C.数据预处理 D.数据流挖掘 4. 当不知道数据所带标签时,可以使用哪种技术促使带同类标签的数据与带其他标签的数据相分离?(B) A. 分类 B. 聚类 C. 关联分析 D. 隐马尔可夫链 5. 什么是KDD? (A) A. 数据挖掘与知识发现 B. 领域知识发现 https://cloud.tencent.com/developer/article/1045567
13.ai有什么功能AI有什么功能 AI,即人工智能,是一种广泛应用的科学技术,它具有许多功能,为人类带来了巨大的便利和进步。下面将详细介绍AI的主要功能。 1. 机器学习与数据挖掘 AI的核心功能之一是机器学习和数据挖掘。通过机器学习算法,AI可以自动学习并优化数据处理和分类,从而实现自动化决策和预测。此外,AI还可以从大量数据中提取有https://tool.a5.cn/article/show/78399.html
14.数据挖掘150道试题5. 什么是KDD? (A) A. 数据挖掘与知识发现 B. 领域知识发现 C. 文档知识发现 D. 动态知识发现 6. 使用交互式的和可视化的技术,对数据进行探索属于数据挖掘的哪一类任务?(A) A. 探索性数据分析 B. 建模描述 C. 预测建模 D. 寻找模式和规则 https://www.jianshu.com/p/37b5d84a3481
15.物流服务质量评价模型与方法研究综述图2为结合网络爬虫的数据挖掘技术流程。 3.情感工程法(AE) 情感工程法可将人的感受转化为适当的产品设计元素。与针对客户显性需求的质量功能开发法以及其他多种分析方法联合使用不同,情感工程法用于衡量客户的隐性需求并将其与产品属性相关联[ 100 ]。最重要的是,情感工程法寻求确定感知响应与设计属性之间的因果关系https://www.fx361.com/page/2021/0224/7663320.shtml
16.独家15万元搞定NOA功能,吉利如何扮演成本杀手?那现在我们可以挖掘传感器性能,不断优化算法,让算法跑的更高效,可以加入更多的功能。同时通过我们的数据挖掘不断优化用户体验。 所以说软硬件分离是一个行业趋势。还有这么做的话,我觉得对主机厂来说是非常有意义的,我们要搭建一个比较开放性的平台,实现软硬件的分离,让软件匹配有一个更好的选择。 https://www.yoojia.com/article/8557362526088586124.html
17.研究生开题报告集合14篇本研究拟采用以下的研究方法:首先,结合木材微观识别知识,研究木材显微构造特有的导管、木射线、轴向薄壁组织等对象特征的提取,这些特征本身具有很强的分类能力,通过量化将进一步提高其分类能力;其次,提取显微图像的全局性纹理特征并验证其识别能力;并根据木材特性,进行可微结构性纹理的描述和提取,挖掘出轴向薄壁组织的提取https://www.ruiwen.com/kaitibaogao/7158933.html