欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java線程池配置的一些常見(jiàn)誤區(qū)總結(jié)

 更新時(shí)間:2021年01月05日 11:47:17   作者:枕邊書(shū)  
這篇文章主要給大家介紹了關(guān)于Java線程池配置的一些常見(jiàn)誤區(qū),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

由于線程的創(chuàng)建和銷毀對(duì)操作系統(tǒng)來(lái)說(shuō)都是比較重量級(jí)的操作,所以線程的池化在各種語(yǔ)言內(nèi)都有實(shí)踐,當(dāng)然在 Java 語(yǔ)言中線程池是也非常重要的一部分,有 Doug Lea 大神對(duì)線程池的封裝,我們使用的時(shí)候是非常方便,但也可能會(huì)因?yàn)椴涣私馄渚唧w實(shí)現(xiàn),對(duì)線程池的配置參數(shù)存在誤解。

我們經(jīng)常在一些技術(shù)書(shū)籍或博客上看到,向線程池提交任務(wù)時(shí),線程池的執(zhí)行邏輯如下:

當(dāng)一個(gè)任務(wù)被提交后,線程池首先檢查正在運(yùn)行的線程數(shù)是否達(dá)到核心線程數(shù),如果未達(dá)到則創(chuàng)建一個(gè)線程。
如果線程池內(nèi)正在運(yùn)行的線程數(shù)已經(jīng)達(dá)到了核心線程數(shù),任務(wù)將會(huì)被放到 BlockingQueue 內(nèi)。
如果 BlockingQueue 已滿,線程池將會(huì)嘗試將線程數(shù)擴(kuò)充到最大線程池容量。
如果當(dāng)前線程池內(nèi)線程數(shù)量已經(jīng)達(dá)到最大線程池容量,則會(huì)執(zhí)行拒絕策略拒絕任務(wù)提交。
流程如圖(摘自美團(tuán)技術(shù)博客):

流程描述沒(méi)有問(wèn)題,但如果某些點(diǎn)未經(jīng)過(guò)推敲,容易導(dǎo)致誤解,而且描述中的情境太理想化,如果配置時(shí)不考慮運(yùn)行時(shí)環(huán)境,也會(huì)出現(xiàn)一些非常詭異的問(wèn)題。

轉(zhuǎn)載隨意,文章會(huì)持續(xù)修訂,請(qǐng)注明來(lái)源地址: https://zhenbianshu.github.io 。

核心池

線程池內(nèi)線程數(shù)量小于等于 coreSize 的部分我稱為核心池,核心池是線程池的常駐部分,內(nèi)部的線程一般不會(huì)被銷毀,我們提交的任務(wù)也應(yīng)該絕大部分都由核心池內(nèi)的線程來(lái)執(zhí)行。

線程創(chuàng)建時(shí)機(jī)的誤解

有關(guān)核心池最常見(jiàn)的一個(gè)誤區(qū)是沒(méi)搞清楚核心池內(nèi)線程的創(chuàng)建時(shí)機(jī),這個(gè)問(wèn)題,我覺(jué)得甩 10% 的鍋給 Doug Lea 大神應(yīng)該不算過(guò)分,因?yàn)樗谖臋n里寫(xiě)道 “If fewer than corePoolSize threads are running, try to start a new thread with the given command as its first task”,其中 "running" 這個(gè)詞就比較有歧義,因?yàn)樵谖覀兝斫饫?running 是指當(dāng)前線程已被操作系統(tǒng)調(diào)度,擁有操作系統(tǒng)時(shí)間分片,或者被理解為正在執(zhí)行某個(gè)任務(wù)。

基于以上的理解,我們很容易就認(rèn)為如果任務(wù)的 QPS 非常低,線程池內(nèi)線程數(shù)量永遠(yuǎn)也達(dá)不到 coreSize。 即如果我們配置了 coreSize 為 1000,實(shí)際上 QPS 只有 1,單個(gè)任務(wù)耗時(shí) 1s,那么核心池大小就會(huì)一直是 1,即使有流量抖動(dòng),核心池也只會(huì)被擴(kuò)容到 3。因?yàn)橐粋€(gè)線程每秒執(zhí)行執(zhí)行一個(gè)任務(wù),剛好不用創(chuàng)建新線程就足以應(yīng)對(duì) 1QPS。

創(chuàng)建過(guò)程

但如果簡(jiǎn)單設(shè)計(jì)一個(gè)測(cè)試,使用 jstack 打印出線程棧并數(shù)一下線程池內(nèi)線程數(shù)量,會(huì)發(fā)現(xiàn)線程池內(nèi)的線程數(shù)會(huì)隨著任務(wù)的提交而逐漸增大,直到達(dá)到 coreSize。

因?yàn)楹诵某氐脑O(shè)計(jì)初衷是想它能作為常駐池,承載日常流量,所以它應(yīng)該被盡快初始化,于是線程池的邏輯是在沒(méi)有達(dá)到 coreSize 之前,每一個(gè)任務(wù)都會(huì)創(chuàng)建一個(gè)新的線程,對(duì)應(yīng)的源碼為:

public void execute(Runnable command) {
  ...
  int c = ctl.get();
  if (workerCountOf(c) < corePoolSize) { // workerCountOf() 方法是獲取線程池內(nèi)線程數(shù)量
   if (addWorker(command, true))
    return;
   c = ctl.get();
  }
  ...
 }

而文檔里的 running 狀態(tài)也指的是線程已經(jīng)被創(chuàng)建,我們也知道線程被創(chuàng)建后,會(huì)在一個(gè) while 循環(huán)里嘗試從 BlockingQueue 里獲取并執(zhí)行任務(wù),說(shuō)它正在 running 也不為過(guò)。

基于此,我們對(duì)一些高并發(fā)服務(wù)進(jìn)行的預(yù)熱,其實(shí)并不是期望 JVM 能對(duì)熱點(diǎn)代碼做 JIT 等優(yōu)化,對(duì)線程池、連接池和本地緩存的預(yù)熱才是重點(diǎn)。

BlockingQueue

BlockingQueue 是線程池內(nèi)的另一個(gè)重要組件,首先它是線程池”生產(chǎn)者-消費(fèi)者”模型的中間媒介,另外它也可以為大量突發(fā)的流量做緩沖,但理解和配置它也經(jīng)常會(huì)出錯(cuò)。

運(yùn)行模型

最常見(jiàn)的錯(cuò)誤是不理解線程池的運(yùn)行模型。首先要明確的一點(diǎn)是線程池并沒(méi)有準(zhǔn)確的調(diào)度功能,即它無(wú)法感知有哪些線程是處于空閑狀態(tài)的,并把提交的任務(wù)派發(fā)給空閑線程。線程池采用的是”生產(chǎn)者-消費(fèi)者”模式,除了觸發(fā)線程創(chuàng)建的任務(wù)(線程的 firstTask)不會(huì)入 BlockingQueue 外,其他任務(wù)都要進(jìn)入到 BlockingQueue,等待線程池內(nèi)的線程消費(fèi),而任務(wù)會(huì)被哪個(gè)線程消費(fèi)到完全取決于操作系統(tǒng)的調(diào)度。

對(duì)應(yīng)的生產(chǎn)者源碼如下:

public void execute(Runnable command) {
  ...
  if (isRunning(c) && workQueue.offer(command)) { isRunning() 是判斷線程池處理戚狀態(tài)
   int recheck = ctl.get();
   if (! isRunning(recheck) && remove(command))
    reject(command);
   else if (workerCountOf(recheck) == 0)
    addWorker(null, false);
  }
  ...
 }

對(duì)應(yīng)的消費(fèi)者源碼如下:

private Runnable getTask() {
  for (;;) {
   ...
   Runnable r = timed ?
    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take();
   if (r != null)
    return r;
   ...
  }
 }

BlockingQueue 的緩沖作用

基于”生產(chǎn)者-消費(fèi)者”模型,我們可能會(huì)認(rèn)為如果配置了足夠的消費(fèi)者,線程池就不會(huì)有任何問(wèn)題。其實(shí)不然,我們還必須考慮并發(fā)量這一因素。

設(shè)想以下情況:有 1000 個(gè)任務(wù)要同時(shí)提交到線程池內(nèi)并發(fā)執(zhí)行,在線程池被初始化完成的情況下,它們都要被放到 BlockingQueue 內(nèi)等待被消費(fèi),在極限情況下,消費(fèi)線程一個(gè)任務(wù)也沒(méi)有執(zhí)行完成,那么這 1000 個(gè)請(qǐng)求需要同時(shí)存在于 BlockingQueue 內(nèi),如果配置的 BlockingQueue Size 小于 1000,多余的請(qǐng)求就會(huì)被拒絕。

那么這種極限情況發(fā)生的概率有多大呢?答案是非常大,因?yàn)椴僮飨到y(tǒng)對(duì) I/O 線程的調(diào)度優(yōu)先級(jí)是非常高的,一般我們的任務(wù)都是由 I/O 的準(zhǔn)備或完成(如 tomcat 受理了 http 請(qǐng)求)開(kāi)始的,所以很有可能被調(diào)度到的都是 tomcat 線程,它們?cè)谝恢蓖€程池內(nèi)提交請(qǐng)求,而消費(fèi)者線程卻調(diào)度不到,導(dǎo)致請(qǐng)求堆積。

我負(fù)責(zé)的服務(wù)就發(fā)生過(guò)這種請(qǐng)求被異常拒絕的情況,壓測(cè)時(shí) QPS 2000,平均響應(yīng)時(shí)間為 20ms,正常情況下,40 個(gè)線程就可以平衡生產(chǎn)速度,不會(huì)堆積。但在 BlockingQueue Size 為 50 時(shí),即使線程池 coreSize 為 1000,還會(huì)出現(xiàn)請(qǐng)求被線程池拒絕的情況。

這種情況下,BlockingQueue 的重要的意義就是它是一個(gè)能長(zhǎng)時(shí)間存儲(chǔ)任務(wù)的容器,能以很小的代價(jià)為線程池提供緩沖。根據(jù)上文可知,線程池能支持 BlockingQueue Size 個(gè)任務(wù)同時(shí)提交,我們把最大同時(shí)提交的任務(wù)個(gè)數(shù),稱為并發(fā)量,配置線程池時(shí),了解并發(fā)量異常重要。

并發(fā)量的計(jì)算

我們常用 QPS 來(lái)衡量服務(wù)壓力,所以配置線程池參數(shù)時(shí)也經(jīng)常參考這個(gè)值,但有時(shí)候 QPS 和并發(fā)量有時(shí)候相關(guān)性并沒(méi)有那么高,QPS 還要 搭配任務(wù)執(zhí)行時(shí)間 來(lái) 推算 峰值并發(fā)量。

比如請(qǐng)求間隔嚴(yán)格相同的接口,平均 QPS 為 1000,它的并發(fā)量峰值是多少呢?我們并沒(méi)有辦法估算,因?yàn)槿绻蝿?wù)執(zhí)行時(shí)間為 1ms,那么它的并發(fā)量只有 1;而如果任務(wù)執(zhí)行時(shí)間為 1s,那么并發(fā)量峰值為 1000。

可是知道了任務(wù)執(zhí)行時(shí)間,就能算出并發(fā)量了嗎?也不能,因?yàn)槿绻?qǐng)求的間隔不同,可能 1min 內(nèi)的請(qǐng)求都在一秒內(nèi)發(fā)過(guò)來(lái),那這個(gè)并發(fā)量還要乘以 60,所以上面才說(shuō)知道了 QPS 和任務(wù)執(zhí)行時(shí)間,并發(fā)量也只能靠推算。

計(jì)算并發(fā)量,我一般的經(jīng)驗(yàn)值是 QPS*平均響應(yīng)時(shí)間 ,再留上一倍的冗余,但如果業(yè)務(wù)重要的話,BlockingQueue Size 設(shè)置大一些也無(wú)妨(1000 或以上),畢竟每個(gè)任務(wù)占用的內(nèi)存量很有限。

考慮運(yùn)行時(shí)

GC

除了上面提到的各種情況下,GC 也是一個(gè)很重要的影響因素。

我們都知道 GC 是 Stop the World 的,但這里的 World 指的是 JVM,而一個(gè)請(qǐng)求 I/O 的準(zhǔn)備和完成是操作系統(tǒng)在進(jìn)行的,JVM 停止了,但操作系統(tǒng)還是會(huì)正常受理請(qǐng)求,在 JVM 恢復(fù)后執(zhí)行,所以 GC 是會(huì)堆積請(qǐng)求的。

上文中提到的并發(fā)量計(jì)算一定要考慮到 GC 時(shí)間內(nèi)堆積的請(qǐng)求同時(shí)被受理的情況,堆積的請(qǐng)求數(shù)可以通過(guò) QPS*GC時(shí)間 來(lái)簡(jiǎn)單得出,還有一定要記得留出冗余。

業(yè)務(wù)峰值

除此之外,配置線程池參數(shù)時(shí),一定要考慮業(yè)務(wù)場(chǎng)景。

假如接口的流量大部分來(lái)自于一個(gè)定時(shí)程序,那么平均 QPS 就沒(méi)有了任何意義,線程池設(shè)計(jì)時(shí)就要考慮給 BlockingQueue 的 Size 設(shè)置一個(gè)大一些的值;而如果流量非常不平均,一天內(nèi)只有某一小段時(shí)間才有高流量的話,而且線程資源緊張的情況下,就要考慮給線程池的 maxSize 留下較大的冗余;在流量尖刺明顯而響應(yīng)時(shí)間不那么敏感時(shí),也可以設(shè)置較大的 BlockingQueue,允許任務(wù)進(jìn)行一定程度的堆積。

當(dāng)然除了經(jīng)驗(yàn)和計(jì)算外,對(duì)服務(wù)做定時(shí)的壓測(cè)無(wú)疑更能幫助掌握服務(wù)真實(shí)的情況。

小結(jié)

總結(jié)線程池的配置時(shí),我最大的感受是一定要讀源碼!讀源碼!讀源碼!只看一些書(shū)和文章的總結(jié)是無(wú)法吃透一些重要概念的,即使搞懂了大部分也很容易會(huì)在一些角落踩坑。深入理解原理后,面對(duì)復(fù)雜情況,才有靈活配置的能力。

線程池可討論的點(diǎn)有很多,本文應(yīng)該會(huì)持續(xù)修訂,請(qǐng)關(guān)注原文。

到此這篇關(guān)于Java線程池配置的一些常見(jiàn)誤區(qū)的文章就介紹到這了,更多相關(guān)Java線程池配置誤區(qū)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

參考文獻(xiàn):

Java線程池實(shí)現(xiàn)原理及其在美團(tuán)業(yè)務(wù)中的實(shí)踐

相關(guān)文章

  • SpringBoot整合SpringSession實(shí)現(xiàn)分布式登錄詳情

    SpringBoot整合SpringSession實(shí)現(xiàn)分布式登錄詳情

    這篇文章主要介紹了SpringBoot整合SpringSession實(shí)現(xiàn)分布式登錄詳情,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-08-08
  • Java垃圾回收之復(fù)制算法詳解

    Java垃圾回收之復(fù)制算法詳解

    今天小編就為大家分享一篇關(guān)于Java垃圾回收之復(fù)制算法詳解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-10-10
  • springboot1.X和2.X中如何解決Bean名字相同時(shí)覆蓋

    springboot1.X和2.X中如何解決Bean名字相同時(shí)覆蓋

    這篇文章主要介紹了springboot1.X和2.X中如何解決Bean名字相同時(shí)覆蓋,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 詳解spring security filter的工作原理

    詳解spring security filter的工作原理

    這篇文章主要介紹了詳解spring security filter的工作原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • java實(shí)現(xiàn)文件重命名功能

    java實(shí)現(xiàn)文件重命名功能

    這篇文章主要介紹了java實(shí)現(xiàn)文件重命名功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • Java中的事件處理機(jī)制詳解

    Java中的事件處理機(jī)制詳解

    這篇文章主要介紹了Java中的事件處理機(jī)制詳解,Java事件處理是采取"委派事件模型",當(dāng)事件發(fā)生時(shí),產(chǎn)生事件的對(duì)象,會(huì)把此"信息"傳遞給"事件的監(jiān)聽(tīng)者"處理,這里所說(shuō)的"信息"實(shí)際上就是java.awt.event事件類庫(kù)里某個(gè)類創(chuàng)建對(duì)象,需要的朋友可以參考下
    2023-09-09
  • 使用WebUploader實(shí)現(xiàn)上傳文件功能(一)

    使用WebUploader實(shí)現(xiàn)上傳文件功能(一)

    這篇文章主要為大家詳細(xì)介紹了使用WebUploader實(shí)現(xiàn)上傳文件功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • JAVA Iterator 轉(zhuǎn)成 List 的操作

    JAVA Iterator 轉(zhuǎn)成 List 的操作

    這篇文章主要介紹了JAVA Iterator 轉(zhuǎn)成 List 的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • Java 格式化輸出JSON字符串的2種實(shí)現(xiàn)操作

    Java 格式化輸出JSON字符串的2種實(shí)現(xiàn)操作

    這篇文章主要介紹了Java 格式化輸出JSON字符串的2種實(shí)現(xiàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-10-10
  • Java輸入三個(gè)整數(shù)并把他們由小到大輸出(x,y,z)

    Java輸入三個(gè)整數(shù)并把他們由小到大輸出(x,y,z)

    這篇文章主要介紹了輸入三個(gè)整數(shù)x,y,z,請(qǐng)把這三個(gè)數(shù)由小到大輸出,需要的朋友可以參考下
    2017-02-02

最新評(píng)論