在沒有分析線程池原理之前先來分析下為什么有任務拒絕的情況發生。
這里先假設一個前提:線程池有一個任務隊列,用於緩存所有待處理的任務,正在處理的任務將從任務隊列中移除。因此在任務隊列長度有限的情況下就會出現新任務的拒絕處理問題,需要有一種策略來處理應該加入任務隊列卻因為隊列已滿無法加入的情況。另外在線程池關閉的時候也需要對任務加入隊列操作進行額外的協調處理。
RejectedExecutionHandler提供了四種方式來處理任務拒絕策略
1、直接丟棄(DiscardPolicy)
2、丟棄隊列中最老的任務(DiscardOldestPolicy)。
3、拋異常(AbortPolicy)
4、將任務分給調用線程來執行(CallerRunsPolicy)。
這四種策略是獨立無關的,是對任務拒絕處理的四中表現形式。最簡單的方式就是直接丟棄任務。但是卻有兩種方式,到底是該丟棄哪一個任務,比如可以丟棄當前將要加入隊列的任務本身(DiscardPolicy)或者丟棄任務隊列中最舊任務(DiscardOldestPolicy)。丟棄最舊任務也不是簡單的丟棄最舊的任務,而是有一些額外的處理。除了丟棄任務還可以直接拋出一個異常(RejectedExecutionException),這是比較簡單的方式。拋出異常的方式(AbortPolicy)盡管實現方式比較簡單,但是由於拋出一個RuntimeException,因此會中斷調用者的處理過程。除了拋出異常以外還可以不進入線程池執行,在這種方式(CallerRunsPolicy)中任務將有調用者線程去執行。
下面來看下這幾種拒絕策略的例子。
使用直接丟棄任務本身的拒絕策略:DiscardPolicy
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ExecutorDemo { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) { int corePoolSize = 1; int maximumPoolSize = 1; BlockingQueue queue = new ArrayBlockingQueue<Runnable>(1); ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 0, TimeUnit.SECONDS, queue ) ; pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy ()); for(int i=0;i<10;i++){ final int index = i; pool.submit(new Runnable(){ @Override public void run() { log(Thread.currentThread().getName()+"begin run task :"+index); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log(Thread.currentThread().getName()+" finish run task :"+index); } }); } log("main thread before sleep!!!"); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } log("before shutdown()"); pool.shutdown(); log("after shutdown(),pool.isTerminated=" + pool.isTerminated()); try { pool.awaitTermination(1000L, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } log("now,pool.isTerminated=" + pool.isTerminated()); } protected static void log(String string) { System.out.println(sdf.format(new Date())+" "+string); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
運行結果:
2016-08-04 22:29:21 main thread before sleep!!! 2016-08-04 22:29:21 pool-1-thread-1begin run task :0 2016-08-04 22:29:22 pool-1-thread-1 finish run task :0 2016-08-04 22:29:22 pool-1-thread-1begin run task :1 2016-08-04 22:29:23 pool-1-thread-1 finish run task :1 2016-08-04 22:29:25 before shutdown() 2016-08-04 22:29:25 after shutdown(),pool.isTerminated=false 2016-08-04 22:29:25 now,pool.isTerminated=true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
從結果可以看出,只有task0和task1兩個任務被執行了。
為什么只有task0和task1兩個任務被執行了呢?
過程是這樣的:由於我們的任務隊列的容量為1.當task0正在執行的時候,task1被提交到了隊列中但是還沒有執行,受隊列容量的限制,submit提交的task2~task9就都被直接拋棄了。因此就只有task0和task1被執行了。
使用丟棄任務隊列中比較久的任務的拒絕策略:DiscardOldestPolicy
如果將拒絕策略改為:DiscardOldestPolicy(丟棄隊列中比較久的任務)
運行結果為:
2016-08-04 22:31:58 pool-1-thread-1begin run task :0 2016-08-04 22:31:58 main thread before sleep!!! 2016-08-04 22:31:59 pool-1-thread-1 finish run task :0 2016-08-04 22:31:59 pool-1-thread-1begin run task :9 2016-08-04 22:32:00 pool-1-thread-1 finish run task :9 2016-08-04 22:32:02 before shutdown() 2016-08-04 22:32:02 after shutdown(),pool.isTerminated=false 2016-08-04 22:32:02 now,pool.isTerminated=true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
從結果可以看出,只有task0和task9被執行了。
使用將任務將由調用者線程去執行的拒絕策略:CallerRunsPolicy
如果將拒絕策略改為:CallerRunsPolicy(即不用線程池中的線程執行,而是交給調用方來執行)
運行結果為:
2016-08-04 22:33:07 mainbegin run task :2 2016-08-04 22:33:07 pool-1-thread-1begin run task :0 2016-08-04 22:33:08 main finish run task :2 2016-08-04 22:33:08 mainbegin run task :3 2016-08-04 22:33:08 pool-1-thread-1 finish run task :0 2016-08-04 22:33:08 pool-1-thread-1begin run task :1 2016-08-04 22:33:09 pool-1-thread-1 finish run task :1 2016-08-04 22:33:09 main finish run task :3 2016-08-04 22:33:09 mainbegin run task :5 2016-08-04 22:33:09 pool-1-thread-1begin run task :4 2016-08-04 22:33:10 main finish run task :5 2016-08-04 22:33:10 mainbegin run task :7 2016-08-04 22:33:10 pool-1-thread-1 finish run task :4 2016-08-04 22:33:10 pool-1-thread-1begin run task :6 2016-08-04 22:33:11 main finish run task :7 2016-08-04 22:33:11 mainbegin run task :9 2016-08-04 22:33:11 pool-1-thread-1 finish run task :6 2016-08-04 22:33:11 pool-1-thread-1begin run task :8 2016-08-04 22:33:12 main finish run task :9 2016-08-04 22:33:12 main thread before sleep!!! 2016-08-04 22:33:12 pool-1-thread-1 finish run task :8 2016-08-04 22:33:16 before shutdown() 2016-08-04 22:33:16 after shutdown(),pool.isTerminated=false 2016-08-04 22:33:16 now,pool.isTerminated=true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
從結果可以看出,沒有任務被拋棄,而是將由的任務分配到main線程中執行了。
小結
關於線程池的任務拒絕策略,我們要理解並記住,有如下的四種:
1、直接丟棄(DiscardPolicy)
2、丟棄隊列中最老的任務(DiscardOldestPolicy)。
3、拋異常
4、將任務分給調用線程來執行。
參考資料
1、http://www.blogjava.net/xylz/archive/2011/01/08/342609.html