原文地址 http://blog.csdn.net/qq_25806863/article/details/71172823
在分析ThreadPoolExecutor的構造參數時,有一個RejectedExecutionHandler參數。
RejectedExecutionHandler是一個接口:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
裏面只有一個方法。當要創建的線程數量大於線程池的最大線程數的時候,新的任務就會被拒絕,就會調用這個接口裏的這個方法。
可以自己實現這個接口,實現對這些超出數量的任務的處理。
ThreadPoolExecutor自己已經提供了四個拒絕策略,分別是CallerRunsPolicy
,AbortPolicy
,DiscardPolicy
,DiscardOldestPolicy
這四個拒絕策略其實一看實現方法就知道很簡單。
AbortPolicy
ThreadPoolExecutor中默認的拒絕策略就是AbortPolicy。直接拋出異常。
private static final RejectedExecutionHandler defaultHandler =
new 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異常,也不執行這個任務了。
測試
先自定義一個Runnable,給每個線程起個名字,下面都用這個Runnable
static class MyThread implements Runnable {
String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程:"+Thread.currentThread().getName() +" 執行:"+name +" run");
}
}
然後構造一個核心線程是1,最大線程數是2的線程池。拒絕策略是AbortPolicy
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0,
TimeUnit.MICROSECONDS,
new LinkedBlockingDeque<Runnable>(2),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 6; i++) {
System.out.println("添加第"+i+"個任務");
executor.execute(new MyThread("線程"+i));
Iterator iterator = executor.getQueue().iterator();
while (iterator.hasNext()){
MyThread thread = (MyThread) iterator.next();
System.out.println("列表:"+thread.name);
}
}
輸出是:
分析一下過程。
- 添加第一個任務時,直接執行,任務列表爲空。
- 添加第二個任務時,因爲採用的LinkedBlockingDeque,,並且核心線程正在執行任務,所以會將第二個任務放在隊列中,隊列中有 線程2.
- 添加第三個任務時,也一樣會放在隊列中,隊列中有 線程2,線程3.
- 添加第四個任務時,因爲核心任務還在運行,而且任務隊列已經滿了,所以胡直接創建新線程執行第四個任務,。這時線程池中一共就有兩個線程在運行了,達到了最大線程數。任務隊列中還是有線程2, 線程3.
- 添加第五個任務時,再也沒有地方能存放和執行這個任務了,就會被線程池拒絕添加,執行拒絕策略的rejectedExecution方法,這裏就是執行AbortPolicy的rejectedExecution方法直接拋出異常。
- 最終,只有四個線程能完成運行。後面的都被拒絕了。
CallerRunsPolicy
CallerRunsPolicy在任務被拒絕添加後,會調用當前線程池的所在的線程去執行被拒絕的任務。
下面說他的實現:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
也很簡單,直接run。
測試
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(2),
new ThreadPoolExecutor.AbortPolicy());
按上面的運行,輸出
注意在添加第五個任務,任務5 的時候,同樣被線程池拒絕了,因此執行了CallerRunsPolicy的rejectedExecution方法,這個方法直接執行任務的run方法。因此可以看到任務5是在main線程中執行的。
從中也可以看出,因爲第五個任務在主線程中運行,所以主線程就被阻塞了,以至於當第五個任務執行完,添加第六個任務時,前面兩個任務已經執行完了,有了空閒線程,因此線程6又可以添加到線程池中執行了。
這個策略的缺點就是可能會阻塞主線程。
DiscardPolicy
這個策略的處理就更簡單了,看一下實現就明白了:
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
這個東西什麼都沒幹。
因此採用這個拒絕策略,會讓被線程池拒絕的任務直接拋棄,不會拋異常也不會執行。
測試
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(2),
new ThreadPoolExecutor.DiscardPolicy());
輸出:
可以看到 後面添加的任務5和6根本不會執行,什麼反應都沒有,直接丟棄。
DiscardOldestPolicy
DiscardOldestPolicy策略的作用是,當任務唄拒絕添加時,會拋棄任務隊列中最舊的任務也就是最先加入隊列的,再把這個新任務添加進去。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
在rejectedExecution先從任務隊列總彈出最先加入的任務,空出一個位置,然後再次執行execute方法把任務加入隊列。
測試
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(2),
new ThreadPoolExecutor.DiscardOldestPolicy());
輸出是:
可以看到,
- 在添加第五個任務時,會被線程池拒絕。這時任務隊列中有 任務2,任務3
- 這時,拒絕策略會讓任務隊列中最先加入的任務彈出,也就是任務2.
- 然後把被拒絕的任務5添加人任務隊列,這時任務隊列中就成了 任務3,任務5.
- 添加第六個任務時會因爲同樣的過程,將隊列中的任務3拋棄,把任務6加進去,任務隊列中就成了 任務5,任務6
- 因此,最終能被執行的任務只有1,4,5,6. 任務2和任務3倍拋棄了,不會執行。
自定義拒絕策略
通過看前面的系統提供的四種拒絕策略可以看出,拒絕策略的實現都非常簡單。自己寫亦一樣
比如現在想讓被拒絕的任務在一個新的線程中執行,可以這樣寫:
static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
new Thread(r,"新線程"+new Random().nextInt(10)).start();
}
}
然後正常使用:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(2),
new MyRejectedExecutionHandler());
輸出:
發現被拒絕的任務5和任務6都在新線程中執行了。