ExecutorCompletionService

1.官方文檔

A CompletionService that uses a supplied Executor to execute 
tasks. This class arranges that submitted tasks are, upon 
completion, placed on a queue accessible using take. The class 
is lightweight enough to be suitable for transient use when 
processing groups of tasks.

Usage Examples. Suppose you have a set of solvers for a 
certain problem, each returning a value of some type Result, 
and would like to run them concurrently, processing the results 
of each of them that return a non-null value, in some method 
use(Result r). You could write this as:

一個CompletionService,它使用提供的Executor來執行任務。 該類安排完成時提交的任務放置在可使用take訪問的隊列中。 該類足夠輕巧,適合在處理任務組時進行臨時使用。

用法示例。 假設你有一組針對某個問題的solvers,每個都返回某個類型的Result值,並希望併發運行它們,處理每個返回非空值的結果,在某些方法use(Result r)。 你可以這樣寫:

 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);
     }
 }
Suppose instead that you would like to use the first non-null 
result of the set of tasks, ignoring any that encounter 
exceptions, and cancelling all other tasks when the first one is 
ready:

相反,假設您希望使用任務集的第一個非null結果,忽略任何遇到異常,並在第一個任務準備就緒時取消所有其他任務:

 void solve(Executor e,
            Collection<Callable<Result>> solvers)
     throws InterruptedException {
     CompletionService<Result> ecs
         = new ExecutorCompletionService<Result>(e);
     int n = solvers.size();
     List<Future<Result>> futures
         = new ArrayList<Future<Result>>(n);
     Result result = null;
     try {
         for (Callable<Result> s : solvers)
             futures.add(ecs.submit(s));
         for (int i = 0; i < n; ++i) {
             try {
                 Result r = ecs.take().get();
                 if (r != null) {
                     result = r;
                     break;
                 }
             } catch (ExecutionException ignore) {}
         }
     }
     finally {
         for (Future<Result> f : futures)
             f.cancel(true);
     }

     if (result != null)
         use(result);
 }

2.爲什麼引入ExecutorCompletionService

控制任務組的傳統方式:

List<Callable<T>> tasks = ...;
List<Future<T>> results = executor.invokeAll(tasks);
for (Future<T> result : results) {
  processFurther(result.get());
}

缺點是如果前面的任務花了很多時間,則不得不進行等待。

將結果按照可獲得的順序保存起來更有實際意義。可以用ExecutorCompletionService來進行排列。

ExecutorCompletionService<T> service = new ExecutorCompletionService<>(executor);
for (Callable<T> task: tasks) {
  service.submit(task);
}
for (int i = 0; i < tasks.size(); i++) {
  processFurther(service.take().get());
}

3.源碼解析

3.1 構造器

    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;
    }

3.2 submit

    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;
    }
    public Future<V> submit(Runnable task, V result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<V> f = newTaskFor(task, result);
        executor.execute(new QueueingFuture(f));
        return f;
    }
    private class QueueingFuture extends FutureTask<Void> {
        QueueingFuture(RunnableFuture<V> task) {
            super(task, null);
            this.task = task;
        }
        protected void done() { completionQueue.add(task); }
        private final Future<V> task;
    }

newTaskFor一般情況下新建FutureTask對象,所以來看看FutureTask的run方法:

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
    private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }

可以看到當任務完成後,會調用鉤子方法done,該done方法是在QueueingFuture中定義的,即將完成任務的task放入到阻塞隊列中completionQueue。

protected void done() { completionQueue.add(task); }

3.3 take

    public Future<V> take() throws InterruptedException {
        return completionQueue.take();
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章