線程池拒絕策略 開發中常用什么策略:說說你知道多少種線程池拒絕策略.


前言

線程池,相信很多人都有用過,沒用過相信的也有學習過。但是,線程池的拒絕策略,相信知道的人會少許多。

四種線程池拒絕策略

當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize時,如果還有任務到來就會采取任務拒絕策略,通常有以下四種策略:

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋出異常ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新提交被拒絕的任務ThreadPoolExecutor.CallerRunsPolicy:由調用線程(提交任務的線程)處理該任務

線程池默認的拒絕策略

既然有四種拒絕策略可以選擇,那么線程池的默認拒絕策略是什么呢?查看java.util.concurrent.ThreadPoolExecutor類的源碼,我們可以看到:

/** * The default rejected execution handler */private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

線程池的默認拒絕策略為AbortPolicy,即丟棄任務並拋出RejectedExecutionException異常。我們可以通過代碼來驗證這一點,現有如下代碼:

public class ThreadPoolTest {​ public static void main(String[] args) {​ BlockingQueue queue = new ArrayBlockingQueue<>(100); ThreadFactory factory = r -> new Thread(r, "test-thread-pool"); ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, queue, factory); while (true) { executor.submit(() -> { try { System.out.println(queue.size()); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } }); } }​}

這里是一個默認的線程池,沒有設置拒絕策略,設置了最大線程隊列是100。運行代碼:

894114fe1c34e2b21197ce638ed1e713.png

結果是符合預期的,這也證明了線程池的默認拒絕策略是ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。

設置線程池拒絕策略

如果我們想要根據實際業務場景需要,設置其他的線程池拒絕策略,可以通過ThreadPoolExecutor重載的構造方法進行設置:

bde16900379230287056d47af9cd01c9.png

現在的開發中,相信大家都有使用spring,其實我們也可以通過spring提供的org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor構建線程池。如下:

0c8a186db3b5e5a0a82331130dc33e97.png
@Configurationpublic class TaskExecutorConfig implements AsyncConfigurer { /** * Set the ThreadPoolExecutor's core pool size. */ private static final int CORE_POOL_SIZE = 5; /** * Set the ThreadPoolExecutor's maximum pool size. */ private static final int MAX_POOL_SIZE = 5; /** * Set the capacity for the ThreadPoolExecutor's BlockingQueue. */ private static final int QUEUE_CAPACITY = 1000;​ /** * 通過重寫getAsyncExecutor方法,制定默認的任務執行由該方法產生 * 

* 配置類實現AsyncConfigurer接口並重寫getAsyncExcutor方法,並返回一個ThreadPoolTaskExevutor * 這樣我們就獲得了一個基於線程池的TaskExecutor */ @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(CORE_POOL_SIZE); taskExecutor.setMaxPoolSize(MAX_POOL_SIZE); taskExecutor.setQueueCapacity(QUEUE_CAPACITY); taskExecutor.initialize(); taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); return taskExecutor; }}

通過ThreadPoolTaskExecutor的setRejectedExecutionHandler設置拒絕策略即可。

拒絕策略場景分析

AbortPolicy

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。

A handler for rejected tasks that throws a {@code RejectedExecutionException}.

這是線程池默認的拒絕策略,在任務不能再提交的時候,拋出異常,及時反饋程序運行狀態。如果是比較關鍵的業務,推薦使用此拒絕策略,這樣子在系統不能承載更大的並發量的時候,能夠及時的通過異常發現。

DiscardPolicy

ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋出異常。如果線程隊列已滿,則后續提交的任務都會被丟棄,且是靜默丟棄。

A handler for rejected tasks that silently discards therejected task.

使用此策略,可能會使我們無法發現系統的異常狀態。建議是一些無關緊要的業務采用此策略。例如,本人的博客網站統計閱讀量就是采用的這種拒絕策略。

DiscardOldestPolicy

ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新提交被拒絕的任務。

A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.

此拒絕策略,是一種喜新厭舊的拒絕策略。是否要采用此種拒絕策略,還得根據實際業務是否允許丟棄老任務來認真衡量。

CallerRunsPolicy

ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.

如果任務被拒絕了,則由調用線程(提交任務的線程)直接執行此任務,我們可以通過代碼來驗證這一點:

把之前的代碼修改如下:

public static void main(String[] args) {​ BlockingQueue queue = new ArrayBlockingQueue<>(10); ThreadFactory factory = r -> new Thread(r, "test-thread-pool"); ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, queue, factory, new ThreadPoolExecutor.CallerRunsPolicy()); for (int i = 0; i < 1000; i++) { executor.submit(() -> { try { System.out.println(Thread.currentThread().getName() + ":執行任務"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); }}
382c28f64487bea197621cc871ee4861.png

把隊列最大值改為10,打印輸出線程的名稱。執行結果如下:

e7100f7247a05821d56cbe3cb7addc7a.png

通過結果可以看到,主線程main也執行了任務,這正說明了此拒絕策略由調用線程(提交任務的線程)直接執行被丟棄的任務的。

總結

本文介紹和演示了四種線程池拒絕策略,具體使用哪種策略,還得根據實際業務場景才能做出抉擇。


免責聲明!

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



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