Java執行緒池的拒絕策略
一、簡介
jdk1.5 版本新增了JUC併發程式設計包,極大的簡化了傳統的多執行緒開發。前面文章中介紹了執行緒池的使用,連結地址:https://www.cnblogs.com/eric-fang/p/9004020.html
Java執行緒池,是典型的池化思想的產物,類似的還有資料庫的連線池、redis的連線池等。池化思想,就是在初始的時候去申請資源,建立一批可使用的連線,這樣在使用的時候,就不必再進行建立連線資訊的開銷了。舉個生活中鮮明的例子,在去著名洋快餐某基或者某勞的時候,配餐人員是從一箇中間的保溫箱中直接取食材,然後打包就好了。不用再臨時的來了一個單子,又要去拿原材料,又要去進行加工。效率明顯的就是提高了很多。
俗話說 滿而不損則溢,盈而不持則傾。執行緒池既然是容器,那麼必然的會有存滿的情況。在達到某些特定條件的時候,再來請求的話,池子是如何進行請求處理的呢?這裡就引出了池的拒絕策略。一般的資料庫連線池在達到最大連線數的時候會預設的等待特定的設定的時間或者直接就丟擲異常。而本文中要闡述的執行緒池卻並非如此的策略,下面開始展開講解下。
二、執行緒池的拒絕策略
執行緒池中,有三個重要的引數,決定影響了拒絕策略:corePoolSize - 核心執行緒數,也即最小的執行緒數。workQueue - 阻塞佇列 。 maximumPoolSize - 最大執行緒數
當提交任務數大於 corePoolSize 的時候,會優先將任務放到 workQueue 阻塞佇列中。當阻塞佇列飽和後,會擴充執行緒池中執行緒數,直到達到 maximumPoolSize 最大執行緒數配置。此時,再多餘的任務,則會觸發執行緒池的拒絕策略了。
總結起來,也就是一句話,當提交的任務數大於(workQueue.size() + maximumPoolSize ),就會觸發執行緒池的拒絕策略。
三、拒絕策略定義
拒絕策略提供頂級介面 RejectedExecutionHandler ,其中方法 rejectedExecution 即定製具體的拒絕策略的執行邏輯。
jdk預設提供了四種拒絕策略:
CallerRunsPolicy - 當觸發拒絕策略,只要執行緒池沒有關閉的話,則使用呼叫執行緒直接執行任務。一般併發比較小,效能要求不高,不允許失敗。但是,由於呼叫者自己執行任務,如果任務提交速度過快,可能導致程式阻塞,效能效率上必然的損失較大
AbortPolicy - 丟棄任務,並丟擲拒絕執行 RejectedExecutionException 異常資訊。執行緒池預設的拒絕策略。必須處理好丟擲的異常,否則會打斷當前的執行流程,影響後續的任務執行。
DiscardPolicy - 直接丟棄,其他啥都沒有
DiscardOldestPolicy - 當觸發拒絕策略,只要執行緒池沒有關閉的話,丟棄阻塞佇列 workQueue 中最老的一個任務,並將新任務加入
四、測試程式碼
1、AbortPolicy
package com.cfang; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j public class T2 { public static void main(String[] args) throws Exception{ int corePoolSize = 5; int maximumPoolSize = 10; long keepAliveTime = 5; BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(10); RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, handler); for(int i=0; i<100; i++) { try { executor.execute(new Thread(() -> log.info(Thread.currentThread().getName() + " is running"))); } catch (Exception e) { log.error(e.getMessage()); } } executor.shutdown(); } }
executor.execute()提交任務,由於會丟擲 RuntimeException,如果沒有try.catch處理異常資訊的話,會中斷呼叫者的處理流程,後續任務得不到執行(跑不完100個)。可自行測試下,很容易在控制檯console中能檢視到。
2、CallerRunsPolicy
主體程式碼同上,更換拒絕策略:
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
執行後,在控制檯console中能夠看到的是,會有一部分的資料列印,顯示的是 “main is running”,也即體現呼叫執行緒處理。
3、DiscardPolicy
更換拒絕策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
直接丟棄任務,實際執行中,打印出的資訊不會有100條。
4、DiscardOldestPolicy
同樣的,更換拒絕策略:
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
實際執行,打印出的資訊也會少於100條。
五、總結
四種拒絕策略是相互獨立無關的,選擇何種策略去執行,還得結合具體的業務場景。實際工作中,一般直接使用 ExecutorService 的時候,都是使用的預設的 defaultHandler ,也即 AbortPolicy 策略。
&n