Java8源碼閱讀 - Executor、ExecutorService、ExecutorCompletionService

Executor

public interface Executor {
    void execute(Runnable command);
}

Executor抽象提供了一種將任務提交與每個任務的運行機制(包括線程使用、調度)分離的方法,即Runnable代表任務,execute處理調度的邏輯;

static class ThreadPerTaskExecutor implements Executor {
    public void execute(Runnable r) {
        System.out.println("ThreadPerTaskExecutor - execute"); 
        new Thread(r).start();
    }
}

static class DirectExecutor implements Executor {
    public void execute(Runnable r) {
        System.out.println("DirectExecutor - execute");
        r.run();
    }
}

class SerialExecutor implements Executor {
    final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
    final Executor executor;
    Runnable active;
    SerialExecutor(Executor executor) {
        this.executor = executor;
    }
    public synchronized void execute(final Runnable r) {
        System.out.println("task offer ... ");
        tasks.offer(new Runnable() {
            public void run() {
                System.out.println("SerialExecutor - execute ... ");
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (active == null) {
            scheduleNext();
        }
    }
    protected synchronized void scheduleNext() {
        if ((active = tasks.poll()) != null) {
            System.out.println("schedule next task ");
            executor.execute(active);
        }
    }
}

public static void main(String[] args) {
    // SerialExecutor executor = new SerialExecutor(new ThreadPerTaskExecutor());
    SerialExecutor executor = new SerialExecutor(new DirectExecutor());
    executor.execute(() -> { System.out.println(Thread.currentThread()); });
    executor.execute(() -> { System.out.println(Thread.currentThread()); });
}

#輸出
#task offer ... 
#schedule next task 
#DirectExecutor - execute
#SerialExecutor - execute ... 
#Thread[main,5,main]
#task offer ... 
#schedule next task 
#DirectExecutor - execute
#SerialExecutor - execute ... 
#Thread[main,5,main]

官方文檔上提供的示例,演示了Executor抽象的簡單使用方法,示例想體現的除了異步同步任務外,我覺得更加重要的是任務執行和任務調度分離的思想;

ExecutorService

public interface ExecutorService extends Executor {
    // 啟動有序關閉,在此過程中執行以前提交的任務,但不接受任何新任務。
    void shutdown();
    // 嘗試停止所有正在執行的任務,停止等待任務的處理,并返回等待執行的任務列表。
    List<Runnable> shutdownNow();
    // executor是否被shutdown
    boolean isShutdown();
    // 如果所有任務都完成關閉則返回true
    boolean isTerminated();
    //  阻塞直到所有任務在關閉請求后完成執行,或超時發生后,或當前線程中斷后;
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    // 提交任務,返回
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    // 執行一個集合的任務,返回一個列表其中包含所有任務完成時的狀態和結果(可以是異常結果)。
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    // 執行給定的任務,返回已成功完成的任務的結果(即不拋出異常),如果有的話。在正常或異常返回時,未完成的任務將被取消。
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService提供了對每個Executor跟蹤、管理一個或者多個異步任務的進度,也可以關閉服務來終止service接收新任務和回收資源;

void shutdownAndAwaitTermination(ExecutorService pool) {
    pool.shutdown(); // 禁止提交新任務
    try {
        //等待現有任務終止
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            pool.shutdownNow(); // 取消當前正在執行的任務
            // 等待一段時間,等待任務響應被取消
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) 
                System.err.println("Pool did not terminate");
        }
    } catch (InterruptedException ie) {
        // 如果當前線程處于中斷狀態,重試shutdown
        pool.shutdownNow();
        // 保存中斷狀態
        Thread.currentThread().interrupt();
    }
}

文檔中演示了一個終止服務的示例,分兩個階段關閉一個 ExecutorService,首先需要調用shutdown來拒絕傳入的任務,然后調用shutdownNow(如果需要的話)來取消任何延遲的任務;

CompletionService

public interface CompletionService<V> {
    Future<V> submit(Callable<V> task);
    Future<V> submit(Runnable task, V result);
    Future<V> take() throws InterruptedException;
    Future<V> poll();
    Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}

CompletionService旨在提供一個異步任務產生執行和已完成任務結果的解耦服務,即會有一個隊列儲存已完成的任務結果的合集,任務的提交和執行不會阻塞獲取結果操作;

ExecutorCompletionService

public class ExecutorCompletionService<V> implements CompletionService<V> {
    private final Executor executor;
    private final AbstractExecutorService aes;
    private final BlockingQueue<Future<V>> completionQueue;
    ... 
}

ExecutorCompletionServiceCompletionService的實現類,內部有個阻塞隊列儲存的是完成任務的結果;

// 注意這個queue的默認長度為Integer.MAX_VALUE
public ExecutorCompletionService(Executor executor) {
    if (executor == null)
        throw new NullPointerException();
    this.executor = executor;
    this.aes = (executor instanceof AbstractExecutorService) ?
        (AbstractExecutorService) executor : null;
    this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}

public ExecutorCompletionService(Executor executor,
                                 BlockingQueue<Future<V>> completionQueue) {
    if (executor == null || completionQueue == null)
        throw new NullPointerException();
    this.executor = executor;
    this.aes = (executor instanceof AbstractExecutorService) ?
        (AbstractExecutorService) executor : null;
    this.completionQueue = completionQueue;
}

構造器默認使用LinkedBlockingQueue,那么很容易就聯想到該阻塞隊列的特性:

  • 是有界的雙端隊列
  • 提供阻塞和非阻塞方法獲取已完成的任務
public Future<V> take() throws InterruptedException {
    return completionQueue.take();
}
public Future<V> poll() {
    return completionQueue.poll();
}
public Future<V> poll(long timeout, TimeUnit unit)
        throws InterruptedException {
    return completionQueue.poll(timeout, unit);
}
  • 當完成任務的隊列滿了,新完成的任務結果會被拋棄
private class QueueingFuture extends FutureTask<Void> {
    QueueingFuture(RunnableFuture<V> task) {
        super(task, null);
        this.task = task;
    }
    // FutureTask提供的鉤子
    protected void done() { completionQueue.add(task); }
    private final Future<V> task;
}

注意這里的doneFutureTask完成或者異常或者cancel都會被調用;

public Future<V> submit(Callable<V> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<V> f = newTaskFor(task);
    executor.execute(new QueueingFuture(f));
    return f;
}
private RunnableFuture<V> newTaskFor(Callable<V> task) {
    if (aes == null)
        return new FutureTask<V>(task);
    else
        return aes.newTaskFor(task);
}

若構造器中傳進來的ExecutorAbstractExecutorService的子類,那么newTaskFor就會交由子類來決定FutureTask的類型,達到定制擴展的效果;

void solve(Executor e, Collection<Callable<Result>> solvers) 
        throws InterruptedException, ExecutionException {
    CompletionService<Result> ecs = new ExecutorCompletionService<Result>(e);
    for (Callable<Result> s : solvers) 
        ecs.submit(s);
        int n = solvers.size();
        for (int i = 0; i < n; ++i) { 
            Result r = ecs.take().get(); // 這里會拋出中斷異常
            if (r != null) 
                use(r);
        } 
}

官方提供的用法示例,第一個比較簡單,遍歷任務集合中每個任務執行后獲取結果,是一種順序執行的過程,執行 -> 獲取結果 -> 執行 -> 獲取結果,當然執行任務過程可以是異步的,如果遇到中斷異常會停止任務;

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容