詳解Java ThreadPoolExecutor的拒絕策略
背景
線程池的技術在項目中使用廣泛,線程池提供瞭四種拒絕策略,大傢是否瞭解這四種拒絕的策略呢?本文將詳細的講解ThreadPoolExecutor的四種拒絕策略,以及相關的註意事項。
線程池基本原理
線程池的原理如下圖:
說明:
- 當前運行的線程少於corePoolSize,則創建新線程來執行任務。
- 運行的線程等於或多於corePoolSize,則將任務添加到隊列中。
- 當任務隊列已滿,則在非corePool中創建新的線程來處理任務。
- 創建新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法。
線程池拒絕策略
線程池為我們提供瞭四種拒絕策略分別是:CallerRunsPolicy,AbortPolicy,DiscardPolicy,DiscardOldestPolicy
AbortPolicy
ThreadPoolExecutor中默認的拒絕策略就是AbortPolicy直接拋出異常,具體實現如下
public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
說明:這種策略非常簡單粗暴,直接拋出RejectedExecutionException異常,也不會執行後續的任務。
示例說明:
public class ThreadPoolTest { public static void main(String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2, 5, 10, TimeUnit.MICROSECONDS, new LinkedBlockingDeque<>(1), new ThreadPoolExecutor.AbortPolicy()); //異步執行 for(int i=0; i<10;i++) { System.out.println("添加第"+i+"個任務"); threadPoolExecutor.execute(new TestThread("線程"+i)); } } } public class TestThread implements Runnable { private String name; public TestThread(String name){ this.name=name; } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("thread name:"+Thread.currentThread().getName()+",執行:"+name); } }
執行結果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.skywares.fw.juc.thread.TestThread@55f96302 rejected from java.util.concurrent.ThreadPoolExecutor@3d4eac69[Running, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at com.skywares.fw.juc.thread.ThreadPoolTest.main(ThreadPoolTest.java:26)
thread name:pool-1-thread-5,執行:線程5
thread name:pool-1-thread-2,執行:線程1
thread name:pool-1-thread-4,執行:線程4
thread name:pool-1-thread-3,執行:線程3
thread name:pool-1-thread-1,執行:線程0
thread name:pool-1-thread-5,執行:線程2
從執行結果我們得知,采用AbortPolicy策略當任務執行到第七個任務時會直接報錯,導致後續的業務邏輯不會執行。
CallerRunsPolicy
CallerRunsPolicy在任務被拒絕添加後,會用調用execute函數的上層線程去執行被拒絕的任務。
相關示例
public class ThreadPoolTest { public static void main(String[] args) { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2, 5, 10, TimeUnit.MICROSECONDS, new LinkedBlockingDeque<>(1), new ThreadPoolExecutor.CallerRunsPolicy()); //異步執行 for(int i=0; i<10;i++) { System.out.println("添加第"+i+"個任務"); threadPoolExecutor.execute(new TestThread("線程"+i)); } } }
執行結果:
添加第0個任務
添加第1個任務
添加第2個任務
添加第3個任務
添加第4個任務
添加第5個任務
添加第6個任務
thread name:main,執行:線程6
thread name:pool-1-thread-3,執行:線程3
thread name:pool-1-thread-1,執行:線程0
thread name:pool-1-thread-4,執行:線程4
thread name:pool-1-thread-2,執行:線程1
thread name:pool-1-thread-5,執行:線程5
添加第7個任務
添加第8個任務
thread name:main,執行:線程8
thread name:pool-1-thread-1,執行:線程7
thread name:pool-1-thread-3,執行:線程2
添加第9個任務
thread name:pool-1-thread-1,執行:線程9
從執行的結果我們可以得知,當執行到第7個任務時,由於線程池拒絕策略,此任務由主線程來執行,當線程池有空閑時,才繼續執行其他的任務。所以此策略可能會阻塞主線程。
DiscardPolicy
這種拒絕策略比較簡單,線程池拒絕的任務直接拋棄,不會拋異常也不會執行
示例
修改上述的代碼,將拒絕策略修改為DiscardPolicy
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2, 5, 10, TimeUnit.MICROSECONDS, new LinkedBlockingDeque<>(1), new ThreadPoolExecutor.CallerRunsPolicy());
執行結果
invoke dealStock success
goodsId:手機
thread name:pool-1-thread-1,執行:線程0
thread name:pool-1-thread-4,執行:線程4
thread name:pool-1-thread-5,執行:線程5
thread name:pool-1-thread-3,執行:線程3
thread name:pool-1-thread-2,執行:線程1
thread name:pool-1-thread-1,執行:線程2
從執行的結果來看隻執行瞭6個任務,其他的任務都被拋棄瞭。
DiscardOldestPolicy
DiscardOldestPolicy 當任務拒絕添加時,會拋棄任務隊列中最先加入隊列的任務,再把新任務添加進去。
示例說明
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 1, 2, 10, TimeUnit.MICROSECONDS, new LinkedBlockingDeque<>(2), new ThreadPoolExecutor.CallerRunsPolicy());
執行結果:
添加第0個任務
添加第1個任務
添加第2個任務
添加第3個任務
添加第4個任務
添加第5個任務
invoke dealStock success
goodsId:手機
thread name:pool-1-thread-2,執行:線程3
thread name:pool-1-thread-1,執行:線程0
thread name:pool-1-thread-1,執行:線程2
thread name:pool-1-thread-2,執行:線程1
自定義拒絕策略
當線程池提供的拒絕策略無法滿足要求時,我們可以采用自定義的拒絕策略,隻需要實現RejectedExecutionHandler接口即可
public class CustRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { new Thread(r,"線程:"+new Random().nextInt(10)).start(); } } ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 1, 2, 10, TimeUnit.MICROSECONDS, new LinkedBlockingDeque<>(2), new CustRejectedExecutionHandler());
執行結果:
thread name:客戶線程:6,執行:線程5
thread name:pool-1-thread-1,執行:線程0
thread name:客戶線程:8,執行:線程4
thread name:pool-1-thread-2,執行:線程3
thread name:pool-1-thread-1,執行:線程1
thread name:pool-1-thread-2,執行:線程2
從執行的結果來看,被拒絕的任務都在客戶的新線程中執行。
小結
- AbortPolicy:直接拋出異常,後續的任務不會執行
- CallerRunsPolicy:子任務執行的時間過長,可能會阻塞主線程。
- DiscardPolicy:不拋異常,任務直接丟棄
- DiscardOldestPolicy;丟棄最先加入隊列的任務
總結
本文對於線程的池的幾種策略進行詳細的講解,在實際的生產中需要集合相關的場景來選擇合適的拒絕策略,如有疑問,請隨時反饋。
到此這篇關於Java ThreadPoolExecutor的拒絕策略的文章就介紹到這瞭,更多相關Java ThreadPoolExecutor拒絕策略內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java線程池使用AbortPolicy策略
- 徹底搞懂Java多線程(三)
- Java並發編程之Executor接口的使用
- 簡單聊一聊Java線程池ThreadPoolExecutor
- Java線程池的簡單使用方法實例教程