當線程池隊列滿了,任務會如何處理?
引言
大家好,我是你們的朋友小米!今天給大家?guī)硪黄P于Java線程池的面試題分析,希望通過這個故事讓大家更輕松地理解這個問題。
背景故事
在一個繁忙的互聯(lián)網(wǎng)公司,程序員小王正在準備自己的社招面試。面試官看著簡歷,露出了微笑:“你知道線程池吧?”小王點點頭,心想這簡直是太簡單的知識點了!于是他信心滿滿地回答:“當然知道!線程池是用來管理線程的,通過池化來避免頻繁創(chuàng)建銷毀線程的性能損耗?!?面試官瞇了瞇眼:“那好,假設你在一個高并發(fā)的環(huán)境中提交了大量任務,突然隊列滿了,線程池接收不到任務了,怎么辦?”
一聽這個問題,小王頓時腦袋一懵——他從來沒有遇到過這種情況!
其實,這個問題就是線程池的任務隊列滿了以后會發(fā)生什么? 這是很多開發(fā)者可能在實際項目中都忽視的一個細節(jié),但在面試中卻是一個常見且有挑戰(zhàn)性的問題。那么今天,我們就一起通過這個問題來探討一下,Java線程池的“深水區(qū)”到底有多深!
線程池的基礎回顧
圖片
在開始分析之前,咱們還是先回顧一下線程池的基本概念,以免有小伙伴對這個概念不太熟悉。
在Java中,線程池是由java.util.concurrent.Executor接口及其實現(xiàn)類ThreadPoolExecutor來實現(xiàn)的。線程池的核心作用就是將任務提交給線程池,讓線程池中的線程來執(zhí)行這些任務,而不需要每次任務都創(chuàng)建新線程。這樣就能顯著提高性能,減少資源消耗。
線程池的基本組成:
- 核心線程數(shù): 用來執(zhí)行任務的線程數(shù)量,線程池啟動時會創(chuàng)建這個數(shù)量的線程。
- 最大線程數(shù): 線程池中允許存在的最大線程數(shù)量,如果核心線程數(shù)不夠,且任務隊列已滿,線程池會創(chuàng)建新的線程,直到達到最大線程數(shù)。
- 任務隊列: 用來保存待執(zhí)行任務的隊列。如果線程池中的核心線程都忙時,新提交的任務會先存入這個隊列,等待線程空閑出來再執(zhí)行。
- 線程池的飽和策略: 當線程池隊列已滿,且線程池的線程數(shù)已經(jīng)達到最大線程數(shù)時,任務應該如何處理。這里就涉及到我們面試題的關鍵點。
線程池的飽和策略
接下來,我們來重點講解一下,當線程池隊列滿了,且沒有空閑線程時,任務會如何處理? 這時就需要看線程池的飽和策略(RejectedExecutionHandler)了。
Java線程池提供了四種默認的飽和策略,分別是:
1、AbortPolicy(默認策略):
這是最常見也是最“直接”的策略。任務會被直接拒絕,并且拋出RejectedExecutionException異常。這種策略意味著,當任務隊列滿了,線程池的線程數(shù)也達到了最大值,新的任務就會被拒絕并拋出異常。
舉個例子: 假設你提交了一個任務,而線程池的隊列已經(jīng)滿了,且沒有空閑線程可用,線程池會選擇拋出RejectedExecutionException。你如果沒有特別處理,就會看到這個異常,程序就會終止或進入異常處理流程。
2、CallerRunsPolicy:
這個策略比較特別。當線程池的任務隊列滿了并且線程池也沒有空閑線程時,提交任務的線程會自己執(zhí)行這個任務,而不是交給線程池來執(zhí)行。也就是說,調(diào)用者線程會直接執(zhí)行被拒絕的任務,而不會拋出異常。
舉個例子: 你提交了一個任務,線程池的隊列已經(jīng)滿了,且線程池的線程數(shù)也已達到最大值,線程池不會創(chuàng)建新線程,而是把任務“交給”提交任務的線程(即當前執(zhí)行任務的線程)來執(zhí)行。
這種策略可以避免任務丟失,但可能會導致調(diào)用者線程的負載過高,影響程序的整體響應能力。
3、DiscardPolicy:
采用這種策略時,如果線程池隊列滿了并且沒有空閑線程,線程池會悄悄地丟棄這個任務,且不拋出任何異常。任務就像沒提交一樣,不會被執(zhí)行。
舉個例子: 你提交了一個任務,但線程池的隊列已滿,線程池沒有空閑線程。這個任務就被丟棄,線程池會繼續(xù)執(zhí)行已有的任務。這個策略適合不要求任務一定執(zhí)行的場景,但如果丟棄任務是不可接受的,就需要慎用。
4、DiscardOldestPolicy:
這種策略會丟棄隊列中最舊的任務,并嘗試提交當前任務。也就是說,線程池會拋棄最早提交的任務,以便為新的任務騰出位置。
舉個例子: 你提交了一個任務,線程池隊列已滿,且沒有空閑線程。線程池會刪除隊列中最舊的任務(即已經(jīng)排隊很久但還未執(zhí)行的任務),然后嘗試把當前提交的任務放入隊列中。
如何選擇合適的飽和策略?
選擇合適的飽和策略需要結合業(yè)務場景。對于一些對任務執(zhí)行時間有嚴格要求的業(yè)務,丟棄任務顯然不可取,應該選擇CallerRunsPolicy策略,讓任務交給調(diào)用者線程執(zhí)行。而對于一些可以容忍丟棄任務的場景,可以選擇DiscardPolicy或者DiscardOldestPolicy。
總結一下:
- AbortPolicy:任務拒絕,拋出異常。
- CallerRunsPolicy:任務交給提交任務的線程執(zhí)行。
- DiscardPolicy:丟棄任務,不拋異常。
- DiscardOldestPolicy:丟棄最舊的任務,嘗試提交新任務。
如何在代碼中設置線程池的飽和策略?
創(chuàng)建線程池時,我們可以通過ThreadPoolExecutor構造方法中的參數(shù)來指定線程池的飽和策略:
圖片
在這個構造方法中,AbortPolicy就是默認的飽和策略。如果你想使用其他的策略,可以將其替換為CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。
面試官的回答
回到小王的面試場景,面試官問完這個問題后,看到小王略顯慌張的表情,笑著說道:“其實,線程池的飽和策略是非常重要的,很多開發(fā)者在高并發(fā)的場景下往往忽視了這一點,導致任務的丟失或者線程池的異常行為。所以,在選擇線程池的飽和策略時,我們一定要根據(jù)具體的業(yè)務需求來決定,而不是盲目使用默認的策略?!?/span>
小王松了一口氣:“原來如此,之前我并沒有考慮過這個細節(jié),謝謝您的解答!”
面試官點點頭:“好的,你已經(jīng)掌握了線程池的基本知識,接下來我們繼續(xù)……”
END
線程池的飽和策略雖然是一個常見的面試題,但很多開發(fā)者在實際工作中可能并沒有遇到過這樣的情形,或者并沒有深刻理解它的作用。希望通過今天的分析,大家能對這個問題有更清晰的認識。在面試時,如果遇到類似的問題,也能快速應答!