ThreadPoolExecutor的應用和實現分析(續)—— 任務飽和丟棄策略

n8765發表於2015-04-24

在前面三篇文章中,我們已經對ThreadPoolExecutor的應用以及任務處理和生命週期相關的原始碼實現做了整理分析。這篇我們簡要整理下java.util.concurrent包中的RejectedExecutionHandler這個介面和對應的實現類。

0. RejectedExecutionHandler介面

當ThreadPoolExecutor執行任務的時候,如果執行緒池的執行緒已經飽和,並且任務佇列也已滿。那麼就會做丟棄處理,這也是execute()方法實現中的操作,原始碼如下:

1
2
else if (!addWorker(command, false))
        reject(command);

這個reject()方法很簡單,直接呼叫丟棄處理的handler方法的rejectedExecution()。

在java.util.concurrent中,專門為此定義了一個介面,是RejectedExecutionHandler

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

其中只有rejectedExecution()一個方法。返回為void,而引數一個是具體的Runnable任務,另一個則是被提交任務的ThreadPoolExecutor。

凡是實現了這個方法的類都可以作為丟棄處理器在ThreadPoolExecutor物件構造的時候作為引數傳入,這個前面的文章已經提到過了。其中ThreadPoolExecutor給出了4種基本策略的實現。分別是:

  • CallerRunsPolicy
  • AbortPolicy
  • DiscardPolicy
  • DiscardOldestPolicy

下面分別詳細說明。

1. 直接丟棄

這個也是實現最簡單的類,其中的rejectedExecution()方法是空實現,即什麼也不做,那麼提交的任務將會被丟棄,而不做任何處理。

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

這個策略使用的時候要小心,要明確需求。不然不知不覺的任務就丟了。

2. 丟棄最老

和上面的有些類似,也是會丟棄掉一個任務,但是是佇列中最早的。

實現如下:

1
2
3
4
5
6
7
8
9
10
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

注意,會先判斷ThreadPoolExecutor物件是否已經進入SHUTDOWN以後的狀態。之後取出佇列頭的任務並不做任何處理,即丟棄,再重新呼叫execute()方法提交新任務。

3. 廢棄終止

這個RejectedExecutionHandler類和直接丟棄不同的是,不是默默地處理,而是丟擲java.util.concurrent.RejectedExecutionException異常,這個異常是RuntimeException的子類。這個策略實現如下:

1
2
3
4
5
6
7
8
9
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());
    }
}

注意,處理這個異常的執行緒是執行execute()的呼叫者執行緒。

4. 呼叫者執行策略

在這個策略實現中,任務還是會被執行,但執行緒池中不會開闢新執行緒,而是提交任務的執行緒來負責維護任務。

1
2
3
4
5
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

注意,和DiscardOldestPolicy同樣,也會先判斷ThreadPoolExecutor物件的狀態,之後執行任務。這樣處理的一個好處,是讓caller執行緒執行任務,以推遲該執行緒進一步提交新任務,有效的緩解了執行緒池物件飽和的情況。

上面只是SunJDK中提供的4種最基本策略,開發者可以根據具體需求定製。

此外,前文提到ThreadPoolExecutor也可以進行擴充套件。在java.util.concurrent包中有ScheduledThreadPoolExecutor這樣一個類,其擴充套件了ThreadPoolExecutor,實現了一些時間和任務排程相關的方法。這裡我就不具體整理了。

相關文章