<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          今天,說(shuō)一說(shuō)線程池 “動(dòng)態(tài)更新”

          共 6092字,需瀏覽 13分鐘

           ·

          2022-01-21 01:30


          源?/?? ? ? ??文/?


          線程池(Thread Pool)是一種基于?池化思想管理線程的工具。使用線程池可以?減少創(chuàng)建銷毀線程的開銷,避免線程過(guò)多導(dǎo)致系統(tǒng)資源耗盡

          目前線程池被廣泛應(yīng)用于業(yè)務(wù)系統(tǒng),但是業(yè)界內(nèi)對(duì)線程池?初始化參數(shù)并沒(méi)有很好的標(biāo)準(zhǔn)。線上環(huán)境的線程池因?yàn)闃I(yè)務(wù)特殊性遇到一些痛點(diǎn),進(jìn)而引發(fā)了小編對(duì)于線程池使用的一些思考

          線上配置不能合理評(píng)估

          最大的痛點(diǎn)就是無(wú)法正確評(píng)估線程池關(guān)鍵參數(shù)的配置。比如核心線程數(shù)、最大線程數(shù)、阻塞隊(duì)列大小等,一旦上線參數(shù)就無(wú)法更改

          設(shè)想一下,當(dāng)你興致勃勃的對(duì)業(yè)務(wù)使用了線程池之后,有沒(méi)有考慮過(guò)這幾種場(chǎng)景

          1. 核心線程過(guò)小,阻塞隊(duì)列過(guò)小,最大線程過(guò)小,導(dǎo)致接口頻繁拋出拒絕策略異常
          2. 核心線程過(guò)小,阻塞隊(duì)列過(guò)小,最大線程過(guò)大,導(dǎo)致線程調(diào)度開銷增大,處理速度下降。如果遇到周期性突發(fā)流量,更是如此
          3. 核心線程過(guò)小,阻塞隊(duì)列過(guò)大,導(dǎo)致任務(wù)堆積,接口響應(yīng)或者程序執(zhí)行時(shí)間拉長(zhǎng)
          4. 核心線程過(guò)大,導(dǎo)致線程池內(nèi)空閑線程過(guò)多,過(guò)多的占用系統(tǒng)資源,造成資源浪費(fèi)

          上面的某些場(chǎng)景,受其它參數(shù)的影響,并不是絕對(duì)成立

          曾經(jīng)這么考慮過(guò),提前計(jì)算好線程池的各項(xiàng)參數(shù)不就 OK 了么,要什么動(dòng)態(tài)?

          這里說(shuō)一下,大多數(shù)的業(yè)務(wù)場(chǎng)景下,線程池參數(shù)最好的情況是大差不差。什么意思呢,就是當(dāng)業(yè)務(wù)運(yùn)行中時(shí),線程池有少量的資源浪費(fèi)或者觸發(fā)少量的拒絕任務(wù)

          但是,有些業(yè)務(wù)的波動(dòng)并不是可以預(yù)測(cè)的。比如說(shuō)有一家開飯店的老板,周一到周四客人并不多,所以平常也沒(méi)備那么多的菜,湊巧來(lái)了一個(gè)旅游團(tuán)來(lái)吃飯,飯店存量也就捉襟見肘了,而這種突發(fā)情況并不可預(yù)估

          如果業(yè)務(wù)系統(tǒng)遇到上述情況,可能需要根據(jù)突來(lái)的流量重新預(yù)估線程池的參數(shù),將系統(tǒng)重新進(jìn)行發(fā)布并查看當(dāng)前線程池的參數(shù)是否合理,如果不合理極有可能還要再來(lái)一遍流程

          而動(dòng)態(tài)線程池要做的就是將?參數(shù)的修改與系統(tǒng)的發(fā)布進(jìn)行隔離,流程圖如下

          沒(méi)有合理的監(jiān)控

          上面提到出現(xiàn)的參數(shù)不合理場(chǎng)景如何發(fā)現(xiàn)呢,那就是?線程池運(yùn)行時(shí)監(jiān)控

          如果可以知道一部分線程池運(yùn)行時(shí)指標(biāo),可以極大程度上的預(yù)防上述問(wèn)題,這里舉一些例子

          1. 監(jiān)控業(yè)務(wù)線程池的?當(dāng)前負(fù)載以及峰值負(fù)載
          2. 監(jiān)控線程池在不同時(shí)間段?核心線程、最大線程、活躍線程數(shù)量指標(biāo)
          3. 監(jiān)控線程?池阻塞隊(duì)列相關(guān)指標(biāo),判斷是否有任務(wù)積壓的風(fēng)險(xiǎn)
          4. 監(jiān)控線程任務(wù)在?運(yùn)行時(shí)拋出的異常數(shù)量,診斷投遞的任務(wù)是否“健康”
          5. 監(jiān)控線程池執(zhí)行?拒絕策略執(zhí)行的次數(shù),確定線程池參數(shù)是否合理

          如果監(jiān)控搭配上合理的報(bào)警信息,可以極大程度上避免開發(fā)對(duì)于線上業(yè)務(wù)的后知后覺(jué),有效預(yù)防一些問(wèn)題以及提高業(yè)務(wù) BUG 的修復(fù)速度

          上述關(guān)于線程池動(dòng)態(tài)參數(shù)、監(jiān)控以及預(yù)警等思考,源自于美團(tuán)技術(shù)博客?《Java 線程池實(shí)現(xiàn)原理及其在美團(tuán)業(yè)務(wù)中的實(shí)踐》[1]

          如何動(dòng)態(tài)更新參數(shù)

          動(dòng)態(tài)設(shè)置線程池參數(shù)涉及到兩個(gè)問(wèn)題,都有哪些參數(shù)可以動(dòng)態(tài)更新?使用什么方式動(dòng)態(tài)更新?

          這里先列舉下原聲線程池 API 支持修改的參數(shù)集合,然后梳理看看支持修改后有什么好處

          CorePoolSize(核心線程數(shù)量)

          線程池中空閑時(shí)存在最小的線程數(shù)量。可以通過(guò)?#setCorePoolSize?修改線程池核心線程數(shù)量,流程圖如下

          相對(duì)于其它幾個(gè)動(dòng)態(tài)參數(shù),核心線程數(shù)的動(dòng)態(tài)設(shè)置流程還算復(fù)雜一些

          1. 判斷設(shè)置的?new corePoolSize?必須大于 0,否則拋出異常
          2. 直接替換線程池的?corePoolSize?為?new corePoolSize
          3. 判斷線程池的工作線程是否大于?new corePoolSize,條件如果成立則執(zhí)行中斷多余空閑的線程
          4. 如果上述條件不成立,判斷?corePoolSize?是否小于?new corePoolSize,如果小于說(shuō)明需要?jiǎng)?chuàng)建新的核心線程

          關(guān)于第四步,有一個(gè)小知識(shí)點(diǎn),線程池作者為了保證線程資源不浪費(fèi)而做出的優(yōu)化

          執(zhí)行第四步時(shí)通過(guò)注釋得知,并不知道需要?jiǎng)?chuàng)建多少線程,而為了保證線程資源不會(huì)被浪費(fèi),這里會(huì)依據(jù)?workQueue#size?和 delata 來(lái)計(jì)算出需要?jiǎng)?chuàng)建的線程數(shù)量 k

          Math#min?會(huì)返回兩個(gè)值中小的那個(gè),小編想到了三種情況,我們這里來(lái)假設(shè)下

          1. 假設(shè)?workQueue#size == 0,那么 k 也等于零,證明并沒(méi)有阻塞住的任務(wù)需要執(zhí)行,k-- > 0?表達(dá)式并不成立,就不會(huì)執(zhí)行?#addWorker
          2. 假設(shè)?workQueue#size > 0 && < delta,此時(shí)任務(wù)隊(duì)列里有待執(zhí)行任務(wù)。k-- > 0?表達(dá)式成立,一般情況會(huì)創(chuàng)建?workQueue#size?個(gè)新核心線程,二般情況下是線程池里其它線程把?workQueue?的任務(wù)清了,就會(huì)跳出創(chuàng)建流程
          3. 假設(shè)?workQueue#size > 0 && > delta,這種情況最多會(huì)創(chuàng)建 delata 個(gè)新核心線程

          這里也給我們一個(gè)啟發(fā),寫代碼不能只顧著自掃門前雪,而是要從全局的角度去思考代碼有沒(méi)有可以提升的空間

          小編問(wèn)了下自己,核心線程在沒(méi)任務(wù)時(shí)是不會(huì)被回收的,如果核心線程數(shù)設(shè)置的太大,過(guò)去了峰值期豈不是屬于資源浪費(fèi),難道還要自己再把數(shù)量調(diào)整回來(lái)么

          我們可以在創(chuàng)建線程池時(shí)通過(guò)設(shè)置一個(gè)參數(shù)控制。allowsCoreThreadTimeOut?默認(rèn)為 False,即核心線程即使在空閑時(shí)也保持活動(dòng)狀態(tài)。如果為 True,核心線程使用?keepAliveTime?來(lái)超時(shí)等待工作

          核心線程動(dòng)態(tài)的坑

          有一個(gè)很重要的點(diǎn)需要注意,核心線程數(shù)設(shè)置時(shí)可能失效。比如說(shuō),最大線程數(shù)為 5,當(dāng)前線程池內(nèi)活躍線程數(shù)為 5,此時(shí)設(shè)置核心線程數(shù)為 10 的話,一定是不生效的,Why?

          先假設(shè)線程池的運(yùn)行時(shí)狀態(tài)如下,核心線程為 3,最大線程是 5,線程池內(nèi)活躍線程為 5,此時(shí)調(diào)用?#setCorePoolSize?動(dòng)態(tài)設(shè)置核心線程數(shù)為 10

          執(zhí)行完上述操作之后,調(diào)用?#execute?向線程池發(fā)起任務(wù)執(zhí)行,內(nèi)部處理邏輯如下

          1. 判斷當(dāng)前線程池核心數(shù)為 10,當(dāng)前工作線程為 5,那么會(huì)?發(fā)起?#addWorker?添加線程
          2. #addWorker?會(huì)對(duì)?工作線程數(shù)量 + 1,此時(shí)真正意義上并不算此 Worker 添加到線程池
          3. 接下來(lái)會(huì)創(chuàng)建線程的包裝類 Worker 并執(zhí)行 Start,因?yàn)?Worker 本身持有線程對(duì)象,Start 也是操作線程去執(zhí)行任務(wù)
          4. 獲取任務(wù)?#getTask?有一步操作是動(dòng)態(tài)修改核心線程數(shù)不生效的原因,那就是在真正獲取隊(duì)列中任務(wù)執(zhí)行時(shí)會(huì)先?判斷當(dāng)前的工作線程數(shù)量是否大于最大線程
          5. 因?yàn)樯厦鎸?duì)工作線程有 +1 的操作,所以池內(nèi)工作線程數(shù)是 6,條件判斷表達(dá)式成立,接下來(lái)會(huì)對(duì)?工作線程數(shù)量執(zhí)行 -1 操作并銷毀此 Worker

          這里貼一下線程池獲取隊(duì)列任務(wù)?#getTask?的代碼片段,大家粗略看一下

          既然已經(jīng)知道問(wèn)題出在哪里,應(yīng)該如何去解決動(dòng)態(tài)設(shè)置失效呢

          其實(shí)辦法很簡(jiǎn)單,那就是在設(shè)置核心線程的時(shí)候,同時(shí)設(shè)置最大線程數(shù)就可以。只要工作線程不大于最大線程數(shù),那么動(dòng)態(tài)設(shè)置就是有效的

          本小節(jié)參考自?如何設(shè)置線程池參數(shù)?美團(tuán)給出了一個(gè)讓面試官虎軀一震的回答[2]

          MaximumPoolSize(最大線程數(shù))

          表示線程池中可以創(chuàng)建的最大線程數(shù)。通過(guò)?#setMaximumPoolSize?重新設(shè)置最大線程數(shù),修改邏輯如下

          線程池中設(shè)置最大線程數(shù)的源碼比較簡(jiǎn)單,并不包含復(fù)雜的邏輯,流程如下

          1. 判斷?new maximumPoolSize?參數(shù)是否正確,不滿足條件則拋出異常終止流程
          2. 設(shè)置?new maximumPoolSize?替換線程池最大線程數(shù)
          3. 如果線程池工作線程大于?new maximumPoolSize,則對(duì)多余 Worker 發(fā)起中斷流程

          ThreadFactory(線程工廠)

          線程工廠的功能是為線程池創(chuàng)建線程,線程創(chuàng)建時(shí)可以設(shè)置自定義線程?名稱前綴(重要)、設(shè)置是否 daemon 線程、線程 priority 優(yōu)先級(jí)以及線程未捕獲異常的處理方式

          雖然線程工廠可以在運(yùn)行后重新設(shè)置參數(shù),但是并不建議這么做。因?yàn)橐呀?jīng)運(yùn)行的線程不會(huì)因?yàn)楸讳N毀,如果之前運(yùn)行的線程不被銷毀,一個(gè)線程池中極有可能出現(xiàn)兩種不同語(yǔ)義的線程

          示例代碼中創(chuàng)建了一個(gè)線程池,并指定了線程工廠前綴名稱 before。對(duì)線程池運(yùn)行任務(wù)使其內(nèi)部擁有 before 工廠創(chuàng)建線程

          之后新創(chuàng)建一個(gè) after 線程工廠,進(jìn)行替換線程池內(nèi)部工廠,并運(yùn)行任務(wù)創(chuàng)建最大線程數(shù),我們可以查看下日志

          不出所料兩個(gè)線程工廠創(chuàng)建的線程各自為戰(zhàn),并且如果沒(méi)有特殊操作,這種情況會(huì)一直持續(xù)下去 。所以綜上所述,并不建議業(yè)務(wù)中對(duì)線程工廠修改,不然坑的都是自己人~

          其它參數(shù)

          剩余兩個(gè)動(dòng)態(tài)調(diào)整的參數(shù)較為簡(jiǎn)單,就不一一舉例說(shuō)明了,大家看下源碼即可

          • KeepAliveTime
          • RejectedExecutionHandler

          還有一個(gè)很重要的參數(shù)需要?jiǎng)討B(tài)更新,那就是?阻塞隊(duì)列的大小。可能有的小伙伴就會(huì)問(wèn)了,為什么不直接替換阻塞隊(duì)列呢?

          其實(shí)可以實(shí)現(xiàn)直接替換阻塞隊(duì)列,但是如果直接替換會(huì)引發(fā)出很多的問(wèn)題,舉個(gè)最直接的例子,原隊(duì)列中的堆積任務(wù)不好處理,修改容量就能解決問(wèn)題的事情,沒(méi)必要復(fù)雜化。所以在做動(dòng)態(tài)時(shí),考慮的只是阻塞隊(duì)列的大小而不是替換

          這里以?LinkedBlockingQueue?為例,隊(duì)列在源碼中并沒(méi)有提供修改隊(duì)列大小的方法,因?yàn)榇黻?duì)列大小的變量?capacity?被 final 關(guān)鍵字修飾

          大家可以考慮下,基于這種 final 修飾的情況,應(yīng)該如何去擴(kuò)展阻塞隊(duì)列的容量修改

          動(dòng)態(tài)的阻塞隊(duì)列

          線程池中是以?生產(chǎn)者消費(fèi)者模式,通過(guò)一個(gè)阻塞隊(duì)列來(lái)緩存任務(wù),工作線程從阻塞隊(duì)列中獲取任務(wù)。工作隊(duì)列的接口是阻塞隊(duì)列(BlockingQueue),在隊(duì)列為空時(shí),獲取元素的線程會(huì)?等待隊(duì)列變?yōu)榉强?/strong>,當(dāng)隊(duì)列滿時(shí),存儲(chǔ)元素的線程會(huì)?等待隊(duì)列可用

          阻塞隊(duì)列動(dòng)態(tài)設(shè)置隊(duì)列大小,有很多種操作方式。可以按照原邏輯不變加一些擴(kuò)展,也可以在特定方法上進(jìn)行重寫,實(shí)現(xiàn)方式并不固定。下面說(shuō)幾種可以實(shí)現(xiàn)動(dòng)態(tài)阻塞隊(duì)列功能的方案

          1. 復(fù)制阻塞隊(duì)列源代碼實(shí)現(xiàn),添加 #set 方法使 capacity 可變
          2. 繼承阻塞隊(duì)列,并在原基礎(chǔ)上重寫核心方法
          3. 繼承阻塞隊(duì)列,反射動(dòng)態(tài)修改 capacity

          如果不需要重寫原阻塞隊(duì)列獲得額外的功能,小編更傾向于第一種,代碼上會(huì)更簡(jiǎn)潔一些,并且穩(wěn)定

          復(fù)制阻塞隊(duì)列

          這一種方式簡(jiǎn)單粗暴,直接把?LinkedBlockingQueue?代碼復(fù)制出來(lái)一份,改個(gè)新名字?ResizableCapacityLinkedBlockIngQueue,然后把?capacity?所修飾的?final?關(guān)鍵字去掉,再加上一個(gè)?#setCapacity?方法

          重寫核心方法

          網(wǎng)上大多數(shù)博主使用的都是上述復(fù)制阻塞隊(duì)列的方式,后來(lái)和兩位大佬討論阻塞隊(duì)列的動(dòng)態(tài),然后從 GitHub 上發(fā)現(xiàn)了一位國(guó)外程序員的版本,通過(guò)信號(hào)量的方式控制阻塞隊(duì)列的大小,《GitHub LinkedBQ 信號(hào)量實(shí)現(xiàn)》[3]

          隊(duì)列中包含阻塞?隊(duì)列的大小以及自實(shí)現(xiàn)的信號(hào)量。每次進(jìn)行調(diào)整阻塞隊(duì)列大小的同時(shí)也對(duì)信號(hào)量進(jìn)行增減

          反射修改 Capacity

          通過(guò)反射的方式同樣可以達(dá)到阻塞隊(duì)列動(dòng)態(tài)修改的功能。在修改之前,有考慮過(guò)這種方式?會(huì)不會(huì)存在線程不安全的問(wèn)題,對(duì)此使用 Jmeter 線程組和修改 capacity 交替操作,進(jìn)行了幾輪測(cè)試,測(cè)試的結(jié)論是?不存在線程安全問(wèn)題

          對(duì)于使用反射修改阻塞隊(duì)列大小,小編是不推薦的。首先這種硬編碼的方式并不優(yōu)雅,其次并不能百分百保證能兼容后續(xù) JDK 版本

          綜合考慮,雖然反射修改 capacity 可以達(dá)到理想中的效果,但是不建議這么做

          總結(jié)一下

          在文章中,小編總結(jié)了業(yè)界內(nèi)使用線程池普遍存在的情況

          1. 線程池的?參數(shù)無(wú)法執(zhí)行快速動(dòng)態(tài)調(diào)整
          2. 沒(méi)有合理的監(jiān)控進(jìn)而導(dǎo)致?失去主動(dòng)權(quán)?以及?有效預(yù)防潛在問(wèn)題

          基于第一種動(dòng)態(tài)參數(shù)調(diào)整的問(wèn)題寫下了動(dòng)態(tài)線程池系列的第一篇文章。是的,后面會(huì)有更多的動(dòng)態(tài)線程池文章,包括不限于以下幾個(gè) IDEA

          1. 線程池實(shí)時(shí)監(jiān)控如何實(shí)現(xiàn),歷史指標(biāo)數(shù)據(jù)?如何匯總 Admin 展示
          2. 對(duì)接不同平臺(tái)的報(bào)警消息,達(dá)到?可配置、優(yōu)雅的一發(fā)多收效果
          3. 動(dòng)態(tài)線程池可不可以?對(duì)標(biāo)配置中心,對(duì)接 Server 端統(tǒng)一管理觸發(fā)參數(shù)修改

          上面這些功能,其實(shí)在美團(tuán)的動(dòng)態(tài)線程池里都有實(shí)現(xiàn),奈何并沒(méi)有開源。而自己項(xiàng)目中確實(shí)存在這方面的痛點(diǎn),所以只有?重復(fù)造個(gè)輪子

          最初,花了大概三天時(shí)間寫出了一個(gè)依賴中間件的版本,并可以?完成對(duì)接平臺(tái)化的動(dòng)態(tài)線程池。可能是因?yàn)樘?jiǎn)單了,覺(jué)得是不是缺了點(diǎn)什么。后來(lái)在一個(gè)睡不著的晚上腦洞大開,如果不依賴中間件是不是也行?

          繼而就有了動(dòng)態(tài)線程池 DTP(Dynamic-ThreadPool)項(xiàng)目,并且給項(xiàng)目 groupId 起名 io 開頭。目前而言的話,DTP 僅作為小編鍛煉開發(fā)能力以及產(chǎn)品意識(shí)的項(xiàng)目,每天的代碼開發(fā)時(shí)間集中在下班和周六天

          DTP 項(xiàng)目分為兩個(gè)主體,Server 和 SpringBoot Starter,Server 端作為所有客戶端線程池的注冊(cè)中心以及歷史指標(biāo)數(shù)據(jù)存儲(chǔ),供 Admin 調(diào)取展示,Starter 會(huì)作為 Jar 被客戶端所依賴與 Server 端交互

          目前 DTP 已實(shí)現(xiàn) Server 端和 Client 端的?動(dòng)態(tài)參數(shù)更新交互,源碼實(shí)現(xiàn)參考借鑒了 Nacos 2.0 之前的?長(zhǎng)輪詢和事件監(jiān)聽機(jī)制

          既然選擇了不依賴中間件,那么問(wèn)題也就顯而易見了,線上環(huán)境的單點(diǎn)問(wèn)題。因?yàn)橐坏┎渴鸺海瑪?shù)據(jù)在各節(jié)點(diǎn)之間無(wú)法流轉(zhuǎn)廣播,這一塊后面可能會(huì)?參考 Eureka 做分布式的 AP 模型

          最后的最后,看了項(xiàng)目感覺(jué)還不錯(cuò),辛苦小伙伴點(diǎn)個(gè) ?? Star,祝好。

          代碼還在持續(xù)更新,源碼地址:https://github.com/acmenlt/dynamic-threadpool[4]

          參考資料

          [1]

          《Java線程池實(shí)現(xiàn)原理及其在美團(tuán)業(yè)務(wù)中的實(shí)踐》:?https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

          [2]

          如何設(shè)置線程池參數(shù)?美團(tuán)給出了一個(gè)讓面試官虎軀一震的回答:?https://cloud.tencent.com/developer/article/1615007

          [3]

          《GitHub LinkedBQ 信號(hào)量實(shí)現(xiàn)》:?https://sourl.cn/7Uvw88

          [4]

          點(diǎn)擊閱讀原文跳轉(zhuǎn) GitHub:?https://github.com/acmenlt/dynamic-threadpool


          END

          最后送大家一份福利:


          有不少粉絲所在的企業(yè)都在經(jīng)歷數(shù)字化轉(zhuǎn)型,我準(zhǔn)備了一份紅杉資本撰寫的《2021企業(yè)數(shù)字化年度指南》,不論從調(diào)研視角和行業(yè)洞察堪稱“數(shù)字化轉(zhuǎn)型“指南。

          有興趣的企業(yè)負(fù)責(zé)人可以加我好友索取該份重磅報(bào)告。





          一鍵三連「分享」、「點(diǎn)贊」和「在看」

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费亚洲在线观看 | 国产精国产三级国产普通话高清 | 尻屄视频在线 | 羞羞插插无码 | 欧美一级视频 |