新聞中心
一、序

創(chuàng)新互聯(lián)建站專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務,包含不限于成都做網(wǎng)站、成都網(wǎng)站建設、慶元網(wǎng)絡推廣、微信小程序開發(fā)、慶元網(wǎng)絡營銷、慶元企業(yè)策劃、慶元品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)建站為所有大學生創(chuàng)業(yè)者提供慶元建站搭建服務,24小時服務熱線:028-86922220,官方網(wǎng)址:www.cdcxhl.com
public static ExecutorService newThreadPool() { return new ThreadPoolExecutor( 30, 60, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ());}
我們今天就來借這個問題,聊聊線程池中維護的線程,它增長和回收的策略是什么樣的?
二、線程池的策略
2.1 線程池的各項參數(shù)
當我們聊到線程池中線程的增長策略的時候,最抓眼球的就是它的核心線程數(shù)(corePoolSize)和最大線程數(shù)(maximumPoolSize),但是僅看這兩個參數(shù)是不夠全面的,線程數(shù)量的增長,還與任務等待隊列有關(guān)系。
我們先來看看 ThreadPoolExecutor 最全參數(shù)的構(gòu)造方法:
- public ThreadPoolExecutor(
- int corePoolSize,
- int maximumPoolSize,
- long keepAliveTime,
- TimeUnit unit,
- BlockingQueue
workQueue, - ThreadFactory threadFactory,
- RejectedExecutionHandler handler) {
- // ...
- }
簡單解釋一下各個參數(shù)是什么意思:
- corePoolSize:核心線程數(shù);
- maximumPoolSize:線程池的最大線程數(shù);
- keepAliveTime:核心線程數(shù)之外的線程,最大空閑存活的時長;
- unit:keepAliveTime 的時間單位;
- workQueue:線程池的任務等待隊列;
- threadFractory:線程工廠,用來為線程池創(chuàng)建線程;
- handler:拒絕策略,當線程池無法處理任務時的拒絕方式;
這其中很多參數(shù)的配置,都是相互影響的。例如任務等待隊列 workQueue 配置不當,可能導致線程池中的線程,永遠無法增長到核心線程數(shù)(maximumPoolSize)配置的線程數(shù)。
2.2 線程池中線程的增長策略
看到這里你應該就清楚了,線程池線程的增長策略,和 3 個參數(shù)有關(guān)系:
- corePoolSize:核心線程數(shù)
- maximumPoolSize:最大線程數(shù);
- workQueue:等待任務隊列;
它們之前的關(guān)系是這樣的:
接下來我們看看理想情況下,線程池中線程的增長策略。
默認情況下,初始時線程池是空的,當有新任務來了時,線程池開始通過線程工廠(threadFractory)創(chuàng)建線程來處理任務。
新的任務會不斷的觸發(fā)線程池中線程的創(chuàng)建,直到線程數(shù)量達到核心線程數(shù)(corePoolSize),接下來會停止線程的創(chuàng)建,而是將這個新任務放入任務等待隊列(workQueue)。
新任務不斷進入任務等待隊列,當該隊列滿了時,開始重新創(chuàng)建線程處理任務,直到線程池中線程的數(shù)量,到達 maximumPoolSize 配置的數(shù)量。
到這一步時,線程池的線程數(shù)達到最大值,并且沒有空閑的線程,任務隊列也存滿了任務,這時如果還有新的任務進來,就會觸發(fā)線程池的拒絕策略(handler),如果沒有配置拒絕策略就會拋出 RejectedExecutionException 異常。
到這里線程的增長策略就說清楚了,我們可以通過下圖來了解完整的流程。
其中比較關(guān)鍵的就是任務的等待隊列,無論等待隊列的實現(xiàn)結(jié)構(gòu)是什么樣的,只有在它滿的時候,線程池中的線程才會向最大線程數(shù)增長。但是一個能夠滿的隊列,它的前提是必須是一個有界隊列。
這就是文章開頭舉的例子暗藏的坑,我們回顧一下前面構(gòu)造的線程池。
- public static ExecutorService newThreadPool() {
- return new ThreadPoolExecutor(
- 30, 60,
- 60L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue
()); - }
可以看到,這里雖然最大線程數(shù)是大于核心線程數(shù)的,但是它的等待隊列配置的是一個 LinkedBlockingQueue,從名字上可以看出這是一個基于鏈表實現(xiàn)的阻塞隊列,而用它的默認構(gòu)造方法構(gòu)造時,其容量設定為 Integer.MAX_VALUE,可以簡單理解它是一個無界隊列。
- public LinkedBlockingQueue() {
- this(Integer.MAX_VALUE);
- }
- public LinkedBlockingQueue(int capacity) {
- if (capacity <= 0) throw new IllegalArgumentException();
- this.capacity = capacity;
- last = head = new Node
(null); - }
這也就是為什么說,這樣構(gòu)造的線程池,核心線程數(shù)的配置參數(shù),永遠都用不到,因為它的等待隊列永遠沒有滿的時候。
2.3 線程池中線程的收縮策略
線程池中執(zhí)行的任務,總有執(zhí)行結(jié)束的時候。那么線程池當線程池中存在大量空閑線程時,也會有一定的收縮策略,來回收線程池中多余的線程。
線程池中線程的收縮策略,和以下幾個參數(shù)相關(guān):
- corePoolSize:核心線程數(shù);
- maximumPoolSize:線程池的最大線程數(shù);
- keepAliveTime:核心線程數(shù)之外的線程,空閑存活的時長;
- unit:keepAliveTime 的時間單位;
corePoolSize 和 maximumPoolSize 我們比較熟悉了,另外能夠控制它的就是 keepAliveTime 空閑存活時長,以及這個時長的單位。
當線程池中的線程數(shù),超過核心線程數(shù)時。此時如果任務量下降,肯定會出現(xiàn)有一些線程處于無任務執(zhí)行的空閑狀態(tài)。那么如果這個線程的空閑時間超過了 keepAliveTime&unit 配置的時長后,就會被回收。
需要注意的是,對于線程池來說,它只負責管理線程,對于創(chuàng)建的線程是不區(qū)分所謂的「核心線程」和「非核心線程」的,它只對線程池中的線程總數(shù)進行管理,當回收的線程數(shù)達到 corePoolSize 時,回收的過程就會停止。
對于線程池的核心線程數(shù)中的線程,也有回收的辦法,可以通過 allowCoreThreadTimeOut(true) 方法設置,在核心線程空閑的時候,一旦超過 keepAliveTime&unit 配置的時間,也將其回收掉。
- public void allowCoreThreadTimeOut(boolean value) {
- if (value && keepAliveTime <= 0)
- throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
- if (value != allowCoreThreadTimeOut) {
- allowCoreThreadTimeOut = value;
- if (value)
- interruptIdleWorkers();
- }
- }
allowCoreThreadTimeOut() 能被設置的前提是 keepAliveTime 不能為 0。
2.3 查缺補漏
1. 等待隊列還會影響拒絕策略
等待隊列如果配置成了無界隊列,不光影響線程數(shù)量從核心線程數(shù)向最大線程數(shù)的增長,還會導致配置的拒絕策略永遠得不到執(zhí)行。
因為只有在線程池中的工作線程數(shù)量已經(jīng)達到核心線程數(shù),并且此時等待隊列也滿了的情況下,拒絕策略才能生效。
2. 核心線程數(shù)可以被「預熱」
前面提到默認的情況下,線程池中的線程是根據(jù)任務來增長的。但如果有需要,我們也可以提前準備好線程池的核心線程,來應對突然的高并發(fā)任務,例如在搶購系統(tǒng)中就經(jīng)常有這樣的需要。
此時就可以利用 prestartCoreThread() 或者 prestartAllCoreThreads() 來提前創(chuàng)建核心線程,這種方式被我們稱為「預熱」。
3. 對于需要無界隊列的場景,怎么辦?
需求是多變的,我們肯定會碰到需要使用無界隊列的場景,那么這種場景下配置的 maximumPoolSize 就是無效的。
此時就可以參考 Executors 中 newFixedThreadPool() 創(chuàng)建線程池的過程,將 corePoolSize 和 maximumPoolSize 保持一致即可。
- public static ExecutorService newFixedThreadPool(int nThreads) {
- return new ThreadPoolExecutor(
- nThreads, nThreads,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue
()); - }
此時核心線程數(shù)就是最大線程數(shù),只有增長到這個數(shù)量才會將任務放入等待隊列,來保證我們配置的線程數(shù)量都得到了使用。
4. 線程池是公平的嗎?
所謂的公平,就是先到的任務會被先執(zhí)行。這在線程池中,顯然是不公平的。
不提線程池中線程執(zhí)行任務是通過系統(tǒng)去調(diào)度的,這一點就決定了任務的執(zhí)行順序是無法保證的,這就是是非公平的。另外只從線程池本身的角度來看,我們只看提交的任務順序來看,它也是非公平的。
首先前面到的任務,如果線程池的核心線程已經(jīng)分配出去了,此時這個任務就會進入任務隊列,那么如果任務隊列滿了之后,新到的任務會直接由線程池新創(chuàng)建線程去處理,直到線程數(shù)達到最大線程數(shù)。
那么此時,任務隊列中的任務,雖然先添加進線程池等待處理,但是這些任務的處理時機,是晚于后續(xù)新創(chuàng)建線程去處理的任務的,所以說僅從任務的角度,依然是非公平的。
三、小結(jié)時刻
本文我們聊到了線程池中,對于線程數(shù)量的增長和收縮策略。
在這里我們簡單總結(jié)一下:
1. 增長策略。默認情況下,線程池是根據(jù)任務先創(chuàng)建足夠核心線程數(shù)的線程去執(zhí)行任務,當核心線程滿了時將任務放入等待隊列。待隊列滿了的時候,繼續(xù)創(chuàng)建新線程執(zhí)行任務直到到達最大線程數(shù)停止。再有新任務的話,那就只能執(zhí)行拒絕策略或是拋出異常。
2. 收縮策略。當線程池線程數(shù)量大于核心線程數(shù) && 當前有空閑線程 && 空閑線程的空閑時間大于 keepAliveTime 時,會對該空閑線程進行回收,直到線程數(shù)量等于核心線程數(shù)為止。
總之要謹記,慎用無界隊列。
分享題目:漫畫:聊聊線程池中,線程的增長/回收策略
本文路徑:http://m.5511xx.com/article/dpeesei.html


咨詢
建站咨詢
