Java線程池拒絕策略解析
為什么會有拒絕策略?線程池工作中,當任務量很大,超過系統(tǒng)實際承載能力時,如果不去搭理它,系統(tǒng)很可能崩潰,所以jdk內(nèi)置提供了四種線程池的拒絕策略,可以合理解決這種問題。當線程池中線程已用完不能再創(chuàng)建,等待隊列也排滿,如果此時再有新任務,就會觸發(fā)執(zhí)行拒絕策略之一。

一、CallerRunsPolicy(調(diào)用者運行策略)
一般在不允許失敗的、對性能要求不高、并發(fā)量較小的場景下使用,因為線程池一般情況下不會關(guān)閉,也就是提交的任務一定會被運行,但是由于是調(diào)用者線程自己執(zhí)行的,當多次提交任務時,就會阻塞后續(xù)任務執(zhí)行,性能和效率自然就慢了。當觸發(fā)拒絕策略時,只要線程池沒有關(guān)閉,就由提交任務的當前線程處理。
二、AbortPolicy(中止策略)
當觸發(fā)拒絕策略時,它就會直接拋出拒絕執(zhí)行的異常,中止策略就是直接打斷當前執(zhí)行的流程。它沒有特殊的使用場景,但是一點要正確處理拋出的異常。ThreadPoolExecutor中默認的策略就是AbortPolicy,ExecutorService接口的系列ThreadPoolExecutor因為都沒有顯示的設(shè)置拒絕策略,所以默認的都是這個。但是請注意,ExecutorService中的線程池實例隊列都是無界的,也就是說把內(nèi)存撐爆了都不會觸發(fā)拒絕策略。當自己自定義線程池實例時,使用這個策略一定要處理好觸發(fā)策略時拋的異常,因為他會打斷當前的執(zhí)行流程。
三、DiscardPolicy(丟棄策略)
如果你提交的任務無關(guān)緊要,你就可以使用它 。因為它就是個空實現(xiàn),會悄無聲息的吞噬你的的任務。它就是直接靜悄悄的丟棄這個任務,不觸發(fā)任何動作。所以這個策略基本上不用了。
四、DiscardOldestPolicy(棄老策略)
這個策略依然會丟棄任務,丟棄時也是無聲無息,但丟棄的是老的未執(zhí)行的任務,而且是待執(zhí)行優(yōu)先級較高的任務?;谶@個特性,我能想到的場景就是,發(fā)布消息,和修改消息,當消息發(fā)布出去后,還未執(zhí)行,此時更新的消息又來了,這時未執(zhí)行的消息的版本比現(xiàn)在低就可以被丟棄了。因為隊列中還可能存在消息版本更低的消息會排隊執(zhí)行,所以在真正處理消息的時候一定要做好消息的版本比較。此拒絕策略,是一種喜新厭舊的拒絕策略。是否要采用此種拒絕策略,還得根據(jù)實際業(yè)務是否允許丟棄老任務來認真衡量。
第三方實現(xiàn)的拒絕策略
dubbo中的線程拒絕策略
可以看到,當dubbo的工作線程觸發(fā)了線程拒絕后,主要做了三個事情,原則就是盡量讓使用者清楚觸發(fā)線程拒絕策略的真實原因。
輸出了一條警告級別的日志,日志內(nèi)容為線程池的詳細設(shè)置參數(shù),以及線程池當前的狀態(tài),還有當前拒絕任務的一些詳細信息。可以說,這條日志,使用dubbo的有過生產(chǎn)運維經(jīng)驗的或多或少是見過的,這個日志簡直就是日志打印的典范,其他的日志打印的典范還有spring。得益于這么詳細的日志,可以很容易定位到問題所在。
Netty中的線程池拒絕策略
Netty中的實現(xiàn)很像JDK中的CallerRunsPolicy。不同的是,調(diào)用者運行策略是直接在調(diào)用者線程執(zhí)行的任務。而 Netty是新建了一個線程來處理的。所以,Netty的使用面就可以擴展到支持高效率高性能的場景。但也要注意,Netty的實現(xiàn)里,在創(chuàng)建線程時未做任何的判斷約束。
pinpoint中的線程池拒絕策略
pinpoint的拒絕策略實現(xiàn)很有特色,與眾不同。他定義了一個拒絕策略鏈,包裝了一個拒絕策略列表,當觸發(fā)拒絕策略時,會將策略鏈中的rejectedExecution依次執(zhí)行一遍
最后希望讀完本文能讓你對java線程池拒絕策略有更加細致的了解,可以根據(jù)不同的使用場景靈活運用。