日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ThreadPoolExecutor 八种拒绝策略,对的,不是4种!

發布時間:2025/3/20 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ThreadPoolExecutor 八种拒绝策略,对的,不是4种! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊上方?好好學java?,選擇?星標?公眾號

重磅資訊、干貨,第一時間送達今日推薦:2020年7月程序員工資統計,平均14357元,又跌了,扎心個人原創100W+訪問量博客:點擊前往,查看更多

來源:http://rrd.me/en3Wp

前言

談到 Java 的線程池最熟悉的莫過于 ExecutorService 接口了,jdk1.5 新增的 java.util.concurrent 包下的這個 api,大大的簡化了多線程代碼的開發。而不論你用 FixedThreadPool 還是 CachedThreadPool 其背后實現都是ThreadPoolExecutor。ThreadPoolExecutor 是一個典型的緩存池化設計的產物,因為池子有大小,當池子體積不夠承載時,就涉及到拒絕策略。JDK 中已經預設了 4 種線程池拒絕策略,下面結合場景詳細聊聊這些策略的使用場景,以及我們還能擴展哪些拒絕策略。

池化設計思想

池話設計應該不是一個新名詞。我們常見的如 Java 線程池、JDBC 連接池、Redis 連接池等就是這類設計的代表實現。這種設計會初始預設資源,解決的問題就是抵消每次獲取資源的消耗,如創建線程的開銷,獲取遠程連接的開銷等。就好比你去食堂打飯,打飯的大媽會先把飯盛好幾份放那里,你來了就直接拿著飯盒加菜即可,不用再臨時又盛飯又打菜,效率就高了。除了初始化資源,池化設計還包括如下這些特征:池子的初始值、池子的活躍值、池子的最大值等,這些特征可以直接映射到 Java 線程池和數據庫連接池的成員屬性中。

線程池觸發拒絕策略的時機

和數據源連接池不一樣,線程池除了初始大小和池子最大值,還多了一個阻塞隊列來緩沖。數據源連接池一般請求的連接數超過連接池的最大值的時候就會觸發拒絕策略,策略一般是阻塞等待設置的時間或者直接拋異常。而線程池的觸發時機如下圖:

如圖,想要了解線程池什么時候觸發拒絕粗略,需要明確上面三個參數的具體含義,是這三個參數總體協調的結果,而不是簡單的超過最大線程數就會觸發線程拒絕粗略,當提交的任務數大于 corePoolSize 時,會優先放到隊列緩沖區,只有填滿了緩沖區后,才會判斷當前運行的任務是否大于 maxPoolSize,小于時會新建線程處理。大于時就觸發了拒絕策略,總結就是:當前提交任務數大于(maxPoolSize + queueCapacity)時就會觸發線程池的拒絕策略了。

JDK內置4種線程池拒絕策略

拒絕策略接口定義

在分析 JDK 自帶的線程池拒絕策略前,先看下 JDK 定義的 拒絕策略接口,如下:

1public?interface?RejectedExecutionHandler?{ 2????void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?executor); 3}

接口定義很明確,當觸發拒絕策略時,線程池會調用你設置的具體的策略,將當前提交的任務以及線程池實例本身傳遞給你處理,具體作何處理,不同場景會有不同的考慮,下面看 JDK 為我們內置了哪些實現:

CallerRunsPolicy(調用者運行策略)

1????public?static?class?CallerRunsPolicy?implements?RejectedExecutionHandler?{23????????public?CallerRunsPolicy()?{?}45????????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?e)?{6????????????if?(!e.isShutdown())?{7????????????????r.run();8????????????}9????????} 10????}

功能:當觸發拒絕策略時,只要線程池沒有關閉,就由提交任務的當前線程處理。

使用場景:一般在不允許失敗的、對性能要求不高、并發量較小的場景下使用,因為線程池一般情況下不會關閉,也就是提交的任務一定會被運行,但是由于是調用者線程自己執行的,當多次提交任務時,就會阻塞后續任務執行,性能和效率自然就慢了。

AbortPolicy(中止策略)

1????public?static?class?AbortPolicy?implements?RejectedExecutionHandler?{23????????public?AbortPolicy()?{?}45????????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?e)?{6????????????throw?new?RejectedExecutionException("Task?"?+?r.toString()?+7?????????????????????????????????????????????????"?rejected?from?"?+8?????????????????????????????????????????????????e.toString());9????????} 10????}

功能:當觸發拒絕策略時,直接拋出拒絕執行的異常,中止策略的意思也就是打斷當前執行流程

使用場景:這個就沒有特殊的場景了,但是一點要正確處理拋出的異常。ThreadPoolExecutor 中默認的策略就是AbortPolicy,ExecutorService 接口的系列 ThreadPoolExecutor 因為都沒有顯示的設置拒絕策略,所以默認的都是這個。但是請注意,ExecutorService 中的線程池實例隊列都是無界的,也就是說把內存撐爆了都不會觸發拒絕策略。當自己自定義線程池實例時,使用這個策略一定要處理好觸發策略時拋的異常,因為他會打斷當前的執行流程。

DiscardPolicy(丟棄策略)

1????public?static?class?DiscardPolicy?implements?RejectedExecutionHandler?{ 2 3????????public?DiscardPolicy()?{?} 4 5????????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?e)?{ 6????????} 7????}

功能:直接靜悄悄的丟棄這個任務,不觸發任何動作

使用場景:如果你提交的任務無關緊要,你就可以使用它 。因為它就是個空實現,會悄無聲息的吞噬你的的任務。所以這個策略基本上不用了

DiscardOldestPolicy(棄老策略)

1????public?static?class?DiscardOldestPolicy?implements?RejectedExecutionHandler?{23????????public?DiscardOldestPolicy()?{?}45????????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?e)?{6????????????if?(!e.isShutdown())?{7????????????????e.getQueue().poll();8????????????????e.execute(r);9????????????} 10????????} 11????}

功能:如果線程池未關閉,就彈出隊列頭部的元素,然后嘗試執行

使用場景:這個策略還是會丟棄任務,丟棄時也是毫無聲息,但是特點是丟棄的是老的未執行的任務,而且是待執行優先級較高的任務。基于這個特性,我能想到的場景就是,發布消息,和修改消息,當消息發布出去后,還未執行,此時更新的消息又來了,這個時候未執行的消息的版本比現在提交的消息版本要低就可以被丟棄了。因為隊列中還有可能存在消息版本更低的消息會排隊執行,所以在真正處理消息的時候一定要做好消息的版本比較

第三方實現的拒絕策略

Dubbo 中的線程拒絕策略

1public?class?AbortPolicyWithReport?extends?ThreadPoolExecutor.AbortPolicy?{23????protected?static?final?Logger?logger?=?LoggerFactory.getLogger(AbortPolicyWithReport.class);45????private?final?String?threadName;67????private?final?URL?url;89????private?static?volatile?long?lastPrintTime?=?0; 10 11????private?static?Semaphore?guard?=?new?Semaphore(1); 12 13????public?AbortPolicyWithReport(String?threadName,?URL?url)?{ 14????????this.threadName?=?threadName; 15????????this.url?=?url; 16????} 17 18????@Override 19????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?e)?{ 20????????String?msg?=?String.format("Thread?pool?is?EXHAUSTED!"?+ 21????????????????????????"?Thread?Name:?%s,?Pool?Size:?%d?(active:?%d,?core:?%d,?max:?%d,?largest:?%d),?Task:?%d?(completed:?%d),"?+ 22????????????????????????"?Executor?status:(isShutdown:%s,?isTerminated:%s,?isTerminating:%s),?in?%s://%s:%d!", 23????????????????threadName,?e.getPoolSize(),?e.getActiveCount(),?e.getCorePoolSize(),?e.getMaximumPoolSize(),?e.getLargestPoolSize(), 24????????????????e.getTaskCount(),?e.getCompletedTaskCount(),?e.isShutdown(),?e.isTerminated(),?e.isTerminating(), 25????????????????url.getProtocol(),?url.getIp(),?url.getPort()); 26????????logger.warn(msg); 27????????dumpJStack(); 28????????throw?new?RejectedExecutionException(msg); 29????} 30 31????private?void?dumpJStack()?{ 32???????//省略實現 33????} 34}

可以看到,當dubbo的工作線程觸發了線程拒絕后,主要做了三個事情,原則就是盡量讓使用者清楚觸發線程拒絕策略的真實原因

  • 輸出了一條警告級別的日志,日志內容為線程池的詳細設置參數,以及線程池當前的狀態,還有當前拒絕任務的一些詳細信息。可以說,這條日志,使用dubbo的有過生產運維經驗的或多或少是見過的,這個日志簡直就是日志打印的典范,其他的日志打印的典范還有spring。得益于這么詳細的日志,可以很容易定位到問題所在

  • 輸出當前線程堆棧詳情,這個太有用了,當你通過上面的日志信息還不能定位問題時,案發現場的dump線程上下文信息就是你發現問題的救命稻草,這個可以參考《dubbo線程池耗盡事件-"CyclicBarrier惹的禍"》

  • 繼續拋出拒絕執行異常,使本次任務失敗,這個繼承了JDK默認拒絕策略的特性

Netty 中的線程池拒絕策略

1????private?static?final?class?NewThreadRunsPolicy?implements?RejectedExecutionHandler?{2????????NewThreadRunsPolicy()?{3????????????super();4????????}56????????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?executor)?{7????????????try?{8????????????????final?Thread?t?=?new?Thread(r,?"Temporary?task?executor");9????????????????t.start(); 10????????????}?catch?(Throwable?e)?{ 11????????????????throw?new?RejectedExecutionException( 12????????????????????????"Failed?to?start?a?new?thread",?e); 13????????????} 14????????} 15????}

Netty 中的實現很像 JDK 中的 CallerRunsPolicy,舍不得丟棄任務。不同的是,CallerRunsPolicy 是直接在調用者線程執行的任務。而 Netty是新建了一個線程來處理的。所以,Netty的實現相較于調用者執行策略的使用面就可以擴展到支持高效率高性能的場景了。但是也要注意一點,Netty的實現里,在創建線程時未做任何的判斷約束,也就是說只要系統還有資源就會創建新的線程來處理,直到new不出新的線程了,才會拋創建線程失敗的異常

ActiveMQ 中的線程池拒絕策略

1?new?RejectedExecutionHandler()?{2????????????????@Override3????????????????public?void?rejectedExecution(final?Runnable?r,?final?ThreadPoolExecutor?executor)?{4????????????????????try?{5????????????????????????executor.getQueue().offer(r,?60,?TimeUnit.SECONDS);6????????????????????}?catch?(InterruptedException?e)?{7????????????????????????throw?new?RejectedExecutionException("Interrupted?waiting?for?BrokerService.worker");8????????????????????}9 10????????????????????throw?new?RejectedExecutionException("Timed?Out?while?attempting?to?enqueue?Task."); 11????????????????} 12????????????});

activeMq中的策略屬于最大努力執行任務型,當觸發拒絕策略時,在嘗試一分鐘的時間重新將任務塞進任務隊列,當一分鐘超時還沒成功時,就拋出異常

PinPoint 中的線程池拒絕策略

1public?class?RejectedExecutionHandlerChain?implements?RejectedExecutionHandler?{2????private?final?RejectedExecutionHandler[]?handlerChain;34????public?static?RejectedExecutionHandler?build(List<RejectedExecutionHandler>?chain)?{5????????Objects.requireNonNull(chain,?"handlerChain?must?not?be?null");6????????RejectedExecutionHandler[]?handlerChain?=?chain.toArray(new?RejectedExecutionHandler[0]);7????????return?new?RejectedExecutionHandlerChain(handlerChain);8????}9 10????private?RejectedExecutionHandlerChain(RejectedExecutionHandler[]?handlerChain)?{ 11????????this.handlerChain?=?Objects.requireNonNull(handlerChain,?"handlerChain?must?not?be?null"); 12????} 13 14????@Override 15????public?void?rejectedExecution(Runnable?r,?ThreadPoolExecutor?executor)?{ 16????????for?(RejectedExecutionHandler?rejectedExecutionHandler?:?handlerChain)?{ 17????????????rejectedExecutionHandler.rejectedExecution(r,?executor); 18????????} 19????} 20}

pinpoint的拒絕策略實現很有特點,和其他的實現都不同。他定義了一個拒絕策略鏈,包裝了一個拒絕策略列表,當觸發拒絕策略時,會將策略鏈中的rejectedExecution依次執行一遍。

結語

前文從線程池設計思想,以及線程池觸發拒絕策略的時機引出java線程池拒絕策略接口的定義。并輔以JDK內置4種以及四個第三方開源軟件的拒絕策略定義描述了線程池拒絕策略實現的各種思路和使用場景。希望閱讀此文后能讓你對java線程池拒絕策略有更加深刻的認識,能夠根據不同的使用場景更加靈活的應用。

最后,再附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,筆者這幾年及春招的總結,github 1.5k star,拿去不謝! 下載方式1.?首先掃描下方二維碼2.?后臺回復「Java面試」即可獲取

總結

以上是生活随笔為你收集整理的ThreadPoolExecutor 八种拒绝策略,对的,不是4种!的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。