面試題:線程池的4大拒絕策略


拒絕時機

首先,新建線程池時可以指定它的任務拒絕策略,例如:

newThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
   new ThreadPoolExecutor.DiscardOldestPolicy());

以便在必要的時候按照我們的策略來拒絕任務,那么拒絕任務的時機是什么呢?線程池會在以下兩種情況下會拒絕新提交的任務。

第一種情況是當我們調用 shutdown 等方法關閉線程池后,即便此時可能線程池內部依然有沒執行完的任務正在執行,但是由於線程池已經關閉,此時如果再向線程池內提交任務,就會遭到拒絕。
第二種情況是線程池沒有能力繼續處理新提交的任務,也就是工作已經非常飽和的時候。

比如新建一個線程池,使用容量上限為 10 的 ArrayBlockingQueue 作為任務隊列,並且指定線程池的核心線程數為 5,最大線程數為 10,假設此時有 20 個耗時任務被提交,在這種情況下,線程池會首先創建核心數量的線程,也就是5個線程來執行任務,然后往隊列里去放任務,隊列的 10 個容量被放滿了之后,會繼續創建新線程,直到達到最大線程數 10。此時線程池中一共有 20 個任務,其中 10 個任務正在被 10 個線程執行,還有 10 個任務在任務隊列中等待,而且由於線程池的最大線程數量就是 10,所以已經不能再增加更多的線程來幫忙處理任務了,這就意味着此時線程池工作飽和,這個時候再提交新任務時就會被拒絕。

image-20210108151416526

首先看右側上方的隊列部分,你可以看到目前隊列已經滿了,而圖中隊列下方的每個線程都在工作,且線程數已經達到最大值 10,如果此時再有新的任務提交,線程池由於沒有能力繼續處理新提交的任務,所以就會拒絕。

我們了解了線程池拒絕任務的時機,那么我們如何正確地選擇拒絕策略呢?Java 在 ThreadPoolExecutor 類中為我們提供了 4 種默認的拒絕策略來應對不同的場景,都實現了 RejectedExecutionHandler 接口,如圖所示:

image-20210108151305661

拒絕策略

  • 第一種拒絕策略是 AbortPolicy,這種拒絕策略在拒絕任務時,會直接拋出一個類型為 RejectedExecutionException 的 RuntimeException,讓你感知到任務被拒絕了,於是你便可以根據業務邏輯選擇重試或者放棄提交等策略。

  • 第二種拒絕策略是 DiscardPolicy,這種拒絕策略正如它的名字所描述的一樣,當新任務被提交后直接被丟棄掉,也不會給你任何的通知,相對而言存在一定的風險,因為我們提交的時候根本不知道這個任務會被丟棄,可能造成數據丟失。

  • 第三種拒絕策略是 DiscardOldestPolicy,如果線程池沒被關閉且沒有能力執行,則會丟棄任務隊列中的頭結點,通常是存活時間最長的任務,這種策略與第二種不同之處在於它丟棄的不是最新提交的,而是隊列中存活時間最長的,這樣就可以騰出空間給新提交的任務,但同理它也存在一定的數據丟失風險。

  • 第四種拒絕策略是 CallerRunsPolicy,相對而言它就比較完善了,當有新任務提交后,如果線程池沒被關閉且沒有能力執行,則把這個任務交於提交任務的線程執行,也就是誰提交任務,誰就負責執行任務。這樣做主要有兩點好處。

    • 第一點新提交的任務不會被丟棄,這樣也就不會造成業務損失。
    • 第二點好處是,由於誰提交任務誰就要負責執行任務,這樣提交任務的線程就得負責執行任務,而執行任務又是比較耗時的,在這段期間,提交任務的線程被占用,也就不會再提交新的任務,減緩了任務提交的速度,相當於是一個負反饋。在此期間,線程池中的線程也可以充分利用這段時間來執行掉一部分任務,騰出一定的空間,相當於是給了線程池一定的緩沖期。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM