自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

今天,說一說線程池 “動態(tài)更新”

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

[[414514]]

本文轉(zhuǎn)載自微信公眾號「龍臺的技術(shù)筆記」,作者龍臺 。轉(zhuǎn)載本文請聯(lián)系龍臺的技術(shù)筆記公眾號。

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

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

線上配置不能合理評估

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

線程池中空閑時存在最小的線程數(shù)量??梢酝ㄟ^ #setCorePoolSize 修改線程池核心線程數(shù)量,流程圖如下

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

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

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

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

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

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

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

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

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

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

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

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

  1. 執(zhí)行完上述操作之后,調(diào)用 #execute 向線程池發(fā)起任務(wù)執(zhí)行,內(nèi)部處理邏輯如下
  2. 判斷當(dāng)前線程池核心數(shù)為 10,當(dāng)前工作線程為 5,那么會 發(fā)起 #addWorker 添加線程
  3. #addWorker 會對 工作線程數(shù)量 + 1,此時真正意義上并不算此 Worker 添加到線程池
  4. 接下來會創(chuàng)建線程的包裝類 Worker 并執(zhí)行 Start,因為 Worker 本身持有線程對象,Start 也是操作線程去執(zhí)行任務(wù)

獲取任務(wù) #getTask 有一步操作是動態(tài)修改核心線程數(shù)不生效的原因,那就是在真正獲取隊列中任務(wù)執(zhí)行時會先 判斷當(dāng)前的工作線程數(shù)量是否大于最大線程

因為上面對工作線程有 +1 的操作,所以池內(nèi)工作線程數(shù)是 6,條件判斷表達(dá)式成立,接下來會對 工作線程數(shù)量執(zhí)行 -1 操作,并銷毀此 Worker

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

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

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

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

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

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

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

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

ThreadFactory(線程工廠)

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

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

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

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

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

其它參數(shù)

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

  • KeepAliveTime
  • RejectedExecutionHandler

還有一個很重要的參數(shù)需要動態(tài)更新,那就是 阻塞隊列的大小??赡苡械男』锇榫蜁柫?,為什么不直接替換阻塞隊列呢?

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

這里以 LinkedBlockingQueue 為例,隊列在源碼中并沒有提供修改隊列大小的方法,因為代表隊列大小的變量 capacity 被 final 關(guān)鍵字修飾

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

動態(tài)的阻塞隊列

線程池中是以 生產(chǎn)者消費者模式,通過一個阻塞隊列來緩存任務(wù),工作線程從阻塞隊列中獲取任務(wù)。工作隊列的接口是阻塞隊列(BlockingQueue),在隊列為空時,獲取元素的線程會 等待隊列變?yōu)榉强?,?dāng)隊列滿時,存儲元素的線程會 等待隊列可用

阻塞隊列動態(tài)設(shè)置隊列大小,有很多種操作方式??梢园凑赵壿嫴蛔兗右恍U展,也可以在特定方法上進(jìn)行重寫,實現(xiàn)方式并不固定。下面說幾種可以實現(xiàn)動態(tài)阻塞隊列功能的方案

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

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

復(fù)制阻塞隊列

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

重寫核心方法

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

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

反射修改 Capacity

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

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

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

總結(jié)一下

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

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

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

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

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

最初,花了大概三天時間寫出了一個依賴中間件的版本,并可以 完成對接平臺化的動態(tài)線程池??赡苁且驗樘唵瘟耍X得是不是缺了點什么。后來在一個睡不著的晚上腦洞大開,如果不依賴中間件是不是也行?

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

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

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

既然選擇了不依賴中間件,那么問題也就顯而易見了,線上環(huán)境的單點問題。因為一旦部署集群,數(shù)據(jù)在各節(jié)點之間無法流轉(zhuǎn)廣播,這一塊后面可能會 參考 Eureka 做分布式的 AP 模型

最后的最后,看了項目感覺還不錯,辛苦小伙伴點個 ?? Star,祝好。

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

參考資料

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

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

[3]《GitHub LinkedBQ 信號量實現(xiàn)》: https://sourl.cn/7Uvw88

[4]點擊閱讀原文跳轉(zhuǎn) GitHub: https://github.com/longtai94/dynamic-threadpool

 

責(zé)任編輯:武曉燕 來源: 龍臺的技術(shù)筆記
相關(guān)推薦

2021-01-06 17:28:00

MySQL數(shù)據(jù)庫緩存池

2021-06-27 21:10:12

Linux 進(jìn)程控制

2024-02-27 00:10:06

語言Javascript

2018-04-09 15:10:50

測試方法新手軟件

2011-07-26 09:04:44

MySQL Repli數(shù)據(jù)庫負(fù)載均衡

2020-10-30 10:38:50

Python開發(fā)語法

2011-07-25 13:34:08

ORACLEFLASHBACK T

2015-10-23 11:40:08

SaaS應(yīng)用開發(fā)

2023-12-29 10:28:24

SPIJava靈活性

2011-07-25 17:38:32

數(shù)據(jù)存儲一致性模型

2018-01-17 15:15:22

虛擬化IO半虛擬化

2021-03-09 10:05:06

5G運營商技術(shù)

2023-03-13 22:09:59

JavaSpring機制

2022-12-09 18:06:21

2024-10-24 08:31:26

2023-11-29 16:38:12

線程池阻塞隊列開發(fā)

2025-01-09 11:24:59

線程池美團動態(tài)配置中心

2025-02-28 08:46:24

框架微服務(wù)架構(gòu)

2024-12-10 00:00:25

2022-03-14 08:02:08

輕量級動態(tài)線程池
點贊
收藏

51CTO技術(shù)棧公眾號