线程池参数到底要怎么配?
文章目錄
- 1 線程池快速回顧
- 2 現(xiàn)有設(shè)置參數(shù)的方法及不足
- 3 如何設(shè)置核心線程數(shù)(corePoolSize)
- 4 如何設(shè)置最大線程數(shù)(maxPoolSize)
- 5 如何改變等待隊(duì)列長度
想必大家對(duì)Java里面線程池( 類)一定不陌生吧,無論是在日常工作還是面試題里都經(jīng)常會(huì)有它的身影,特別是在當(dāng)前CPU動(dòng)輒就是好多核的背景下,了解并使用線程池已經(jīng)成為一名合格后端開發(fā)的基本功了。
相信大家也一定思考過一個(gè)問題,面對(duì)各種各樣的場景,線程池的參數(shù)到底應(yīng)該怎么設(shè)計(jì)呢?這一定是一個(gè)超級(jí)難以回答的問題,幾天前的我也想不到一個(gè)標(biāo)準(zhǔn)的答案,好在是發(fā)現(xiàn)了美團(tuán)在2020年發(fā)表過的一篇文章,里面給了一個(gè)非常高級(jí)的操作——讓線程池的參數(shù)動(dòng)態(tài)化,這就極大地提高了系統(tǒng)的自適應(yīng)能力。
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
至于為什么我現(xiàn)在才看到=_=,可能因?yàn)槭翘珣辛税伞!!:迷诩皶r(shí)發(fā)現(xiàn),在此基礎(chǔ)上進(jìn)行一些分析,不理解線程池的小伙伴們也不用擔(dān)心,我們首先來回顧一下它的核心思想,在此技術(shù)上介紹如何將參數(shù)動(dòng)態(tài)化起來~
1 線程池快速回顧
《Java 并發(fā)編程的藝術(shù)》中提到了使用線程池的好處,概括起來如下:
- 降低資源損耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀的損耗。
- 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性。使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。
Java里使用線程池,主要就是用的ThreadPoolExecutor類,先來看一下 ThreadPoolExecutor 類中的構(gòu)造方法:
/*** 用給定的初始參數(shù)創(chuàng)建一個(gè)新的ThreadPoolExecutor。*/ public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數(shù)量int maximumPoolSize,//線程池的最大線程數(shù)long keepAliveTime,//當(dāng)線程數(shù)大于核心線程數(shù)時(shí),多余的空閑線程存活的最長時(shí)間TimeUnit unit,//時(shí)間單位BlockingQueue<Runnable> workQueue,//任務(wù)隊(duì)列,用來儲(chǔ)存等待執(zhí)行任務(wù)的隊(duì)列ThreadFactory threadFactory,//線程工廠,用來創(chuàng)建線程,一般默認(rèn)即可RejectedExecutionHandler handler//拒絕策略,當(dāng)提交的任務(wù)過多而不能及時(shí)處理時(shí),我們可以定制策略來處理任務(wù)) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler; }ThreadPoolExecutor 中最重要的參數(shù):
- corePoolSize:核心線程數(shù)。最小可以同時(shí)運(yùn)行的線程數(shù)。
- maximumPoolSize:當(dāng)隊(duì)列中存放的任務(wù)達(dá)到隊(duì)列容量的時(shí)候,當(dāng)前可以同時(shí)運(yùn)行的最大線程數(shù)。
- workQueue:當(dāng)新任務(wù)來的時(shí)候會(huì)先判斷當(dāng)前運(yùn)行的線程數(shù)量是否達(dá)到corePoolSize,如果達(dá)到的話,新任務(wù)就會(huì)被存放在隊(duì)列中。如果workQueue已經(jīng)滿了的話就執(zhí)行拒絕策略。
ThreadPoolExecutor 的其他參數(shù):
- keepAliveTime:當(dāng)線程池中的線程數(shù)量大于 corePoolSize 的時(shí)候,核心線程外的線程不會(huì)立即銷毀,而是會(huì)等待,直到等待的時(shí)間超過了 keepAliveTime 才會(huì)被銷毀。
- unit : keepAliveTime 參數(shù)的時(shí)間單位。
- threadFactory:executor 創(chuàng)建新線程的時(shí)候會(huì)用到。
- handler:拒絕策略。
當(dāng)參數(shù)設(shè)置完畢后,線程池的工作原理具體是什么呢?我們可以通過下面這個(gè)面試題來理解一下:
假設(shè)我們?cè)O(shè)置的線程池參數(shù)為:corePoolSize=10, maximumPoolSize=20,queueSize = 10
20個(gè)并發(fā)任務(wù)過來,有多少個(gè)活躍線程?
10個(gè)。corePoolSize打滿,queueSize 也滿
21個(gè)并發(fā)任務(wù)過來,有多少個(gè)活躍線程?
11個(gè)。corePoolSize打滿,queueSize 也滿還多一個(gè),maximumPoolSize = 20,所以corePoolSize + 1此時(shí)活躍的為11個(gè)。
30個(gè)并發(fā)任務(wù)過來,有多少個(gè)活躍線程?
20個(gè)。corePoolSize打滿,queueSize 也滿,corePoolSize擴(kuò)充至20,此時(shí)有20個(gè)活躍任務(wù)。
31個(gè)并發(fā)任務(wù)過來,有多少個(gè)活躍線程?
20個(gè)。corePoolSize打滿,queueSize 也滿,corePoolSize擴(kuò)充至20還多一個(gè),如果是丟棄策略,此時(shí)有20個(gè)活躍任務(wù)。
上面的流程可以總結(jié)成如下所示的流程圖:(來源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)
2 現(xiàn)有設(shè)置參數(shù)的方法及不足
回顧完線程池的核心技術(shù)點(diǎn)之后就要開始思考本文主要討論的內(nèi)容了:線程池參數(shù)應(yīng)該如何設(shè)置?
如果你把這個(gè)問題輸入到瀏覽器里,極大可能是下面這種答案:
上面的理論看似很華麗,但現(xiàn)實(shí)卻是很殘酷的。。。你會(huì)發(fā)現(xiàn)雖然按照上面的指導(dǎo)思想進(jìn)行配置了,但效果并不能讓人滿意,造成這種后果的原因有很多,包括但不僅限于:
針對(duì)上述問題,美團(tuán)給出的對(duì)應(yīng)的解決方案就是——線程池參數(shù)動(dòng)態(tài)化
那么如何實(shí)現(xiàn)參數(shù)動(dòng)態(tài)化呢?
接觸過微服務(wù)開發(fā)的同學(xué)們可能就會(huì)想到了,我們完全可以借助一個(gè)配置中心來做,這樣就能夠?qū)崿F(xiàn)線程池參數(shù)的動(dòng)態(tài)配置和即時(shí)生效(在阿里內(nèi)部也有一個(gè)專門的中間件,diamond),省去了重新部署程序并發(fā)布的步驟,通常在企業(yè)里這一系列流程下來還是比較費(fèi)時(shí)間的。
(來源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)
3 如何設(shè)置核心線程數(shù)(corePoolSize)
其實(shí) ThreadPoolExecutor 類庫里直接就有這個(gè)方法:
public void setCorePoolSize(int corePoolSize) {if (corePoolSize < 0)throw new IllegalArgumentException();int delta = corePoolSize - this.corePoolSize;this.corePoolSize = corePoolSize;if (workerCountOf(ctl.get()) > corePoolSize)interruptIdleWorkers();else if (delta > 0) {// We don't really know how many new threads are "needed".// As a heuristic, prestart enough new workers (up to new// core size) to handle the current number of tasks in// queue, but stop if queue becomes empty while doing so.int k = Math.min(delta, workQueue.size());while (k-- > 0 && addWorker(null, true)) {if (workQueue.isEmpty())break;}} }
我們直接看英文注釋,這就是作者直接想要表達(dá)的意思。大致翻譯一下:
設(shè)置線程的核心數(shù)量,如果新的corePoolSize值小于當(dāng)前corePoolSize值,多出來的線程將在其下次空閑時(shí)被終止。如果新的corePoolSize值大于當(dāng)前corePoolSize值,就可以創(chuàng)建新的worker來執(zhí)行隊(duì)列里的任務(wù)
(來源于https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)
4 如何設(shè)置最大線程數(shù)(maxPoolSize)
同樣地 ThreadPoolExecutor 類庫里也有這個(gè)方法:
public void setMaximumPoolSize(int maximumPoolSize) {if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)throw new IllegalArgumentException();this.maximumPoolSize = maximumPoolSize;if (workerCountOf(ctl.get()) > maximumPoolSize)interruptIdleWorkers(); }這個(gè)方法的注釋和上面的方法類似,大家可以對(duì)照著看:
邏輯也并不復(fù)雜:
JDK原生線程池ThreadPoolExecutor還提供了其他設(shè)置參數(shù)的方法:
5 如何改變等待隊(duì)列長度
等待隊(duì)列的長度capacity被final修飾符修飾,所以按理說是不能修改的
private static final int CAPACITY = (1 << COUNT_BITS) - 1;唯一可能的辦法就是自己定義一個(gè)隊(duì)列,在美團(tuán)的實(shí)現(xiàn)里就是一個(gè)名為ResizableCapacityLinkedBlockIngQueue的隊(duì)列,根據(jù)名稱也不難看出,這個(gè)隊(duì)列的容量是可變的。
具體的實(shí)現(xiàn)細(xì)節(jié)美團(tuán)好像并沒有公布出來,不過我們可以簡單的將原先LinkedBlockingQueue的capacity的final修飾符去掉,并提供getter和setter方法,形成我們自己的ResizableCapacityLinkedBlockIngQueue
總結(jié)
以上是生活随笔為你收集整理的线程池参数到底要怎么配?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AI理论知识整理(13)-标准基
- 下一篇: AI理论知识整理(14)-矩阵的秩