Java Thread&Concurrency(8): 深入理解CompletionService接口及其實現

背景(註釋):

我們接着來看下CompletionService接口和它的實現ExecutorCompletionService。

CompletionService接口提供了一種在產生的異步任務和任務的計算結果之間解耦的服務。生產者通過submit提交任務。消費者通過take方法得到已經被執行的任務然後通過他們的完成次序來處理他們的結果。一個CompletionService能夠在異步IO的場景中使用,這種情況下任務在程序的一個地方中提交,但是在另一個地方檢查是否完成,完成的順序與提交順序不同。

一般情況下,CompletionService依賴於一個獨立的Executor去實際地執行任務。CompletionService僅僅只是維護一個內部的完成隊列。ExecutorCompletionService提供了一個這樣的實現。

內存一致性:一個線程提交一個任務前的行爲happen-before任務執行的行爲happen-before通過take操作得到結果之後的行爲。

ExecutorCompletionService類通過一個獨立的Executor來執行任務。這個類將被提交的任務,在完成之後放入一個隊列(通過tak方法取得)。這個類是足夠輕量的(所以是短暫的),從而可以實現大量的任務處理。


使用場景:

假設你有一大羣解決者來解決一個問題,每個返回一個結果Result,並且併發地執行任務。我們需要在其中的解決者完成任務後儘快處理結果(使用use),那麼你可以使用如下示例:

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

假設我們需要處理第一個非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);
  }

實現原理:

這部分代碼較簡單,可以放下面一起說明:

    private final Executor executor;
    private final AbstractExecutorService aes;
    private final BlockingQueue<Future<V>> completionQueue;

    /**
     * FutureTask extension to enqueue upon completion
     */
    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;
    }
三個基本的要素:

  • executor用於實際上執行任務。
  • aes用於創建任務(一般情況下等同於executor對象)。
  • completionQueue爲服務的完成隊列。
這裏的QueueingFuture作爲FutureTask的一個擴展,它實際上只做一件事:在任務完成後把任務添加到服務私有的CompletionQueue完成隊列中,然後就可以從中獲取。

我們來看提交(submit)和獲取(take)方法:
    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;
    }

實際提交的是QueueingFuture對象。

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

提供了從中獲取Future對象的接口(阻塞、非阻塞、限時阻塞)。
注意,這裏得到的返回的是Future,並不是result,Future實際上就是submit中創建的RunnableFuture。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章