Java併發之CompletionService詳解

淡墨痕發表於2021-02-24

CompletionService是什麼?

它是JUC包中的一個介面類,預設實現類只有一個ExecutorCompletionService。

 

CompletionService幹什麼的?

它將非同步任務的生成和執行結果的處理進行了解耦,用來執行Callable的任務(實際也是通過Executor執行緒池執行的,只是它又加了一層封裝),我們只需要呼叫它的take(阻塞)/poll(非阻塞)方法便可以獲取到執行完的任務結果,最先獲取到的必定是先執行完的非同步任務結果。

主要應用場景:同時執行多個Callable任務,並且需對任務的返回結果進行處理。若想優先處理先執行完的任務結果,使用它尤其方便

 

ExecutorCompletionService 原始碼解析

有三個成員變數,關鍵的是下面標註的變數1、變數2:

1 public class ExecutorCompletionService<V> implements CompletionService<V> {
2     private final Executor executor; // 變數1: 執行緒池
3     private final AbstractExecutorService aes;
4     private final BlockingQueue<Future<V>> completionQueue; // 變數2: 阻塞佇列

兩個構造器,如下,用於初始化上面的三個成員變數,可以看到Executor執行緒池是必傳的:

1     public ExecutorCompletionService(Executor executor) {
2         if (executor == null)
3             throw new NullPointerException();
4         this.executor = executor;
5         this.aes = (executor instanceof AbstractExecutorService) ?
6             (AbstractExecutorService) executor : null;
7         this.completionQueue = new LinkedBlockingQueue<Future<V>>();
8     }
1     public ExecutorCompletionService(Executor executor,
2                                      BlockingQueue<Future<V>> completionQueue) {
3         if (executor == null || completionQueue == null)
4             throw new NullPointerException();
5         this.executor = executor;
6         this.aes = (executor instanceof AbstractExecutorService) ?
7             (AbstractExecutorService) executor : null;
8         this.completionQueue = completionQueue;
9     }

核心方法submit,把task封裝成一個QueueingFuture,作為執行任務交給執行緒池執行:

1     public Future<V> submit(Callable<V> task) {
2         if (task == null) throw new NullPointerException();
3         RunnableFuture<V> f = newTaskFor(task);
4         executor.execute(new QueueingFuture(f));
5         return f;
6     }

下面再來看一下QueueingFuture物件,也是一個核心物件,如下所示。QueueingFuture是ExecutorCompletionService的私有內部類,它重寫了FutureTask的done()方法。當任務執行完成set值的時候,會呼叫done方法,在done方法中將task存入阻塞佇列。先執行完的任務就會先放入阻塞佇列,所以我們從佇列中取結果的時候,必定是先取到先執行完的任務。

1     private class QueueingFuture extends FutureTask<Void> {
2         QueueingFuture(RunnableFuture<V> task) {
3             super(task, null);
4             this.task = task;
5         }
6         protected void done() { completionQueue.add(task); }
7         private final Future<V> task;
8     }

總結一下,ExecutorCompletionService是通過QueueingFuture的done方法和阻塞佇列實現的按照非同步任務返回順序來返回結果

 

ExecutorCompletionService和ExecutorService的使用demo示例

Callable類:

 1 class CsCallable implements Callable<String> {
 2     private String name;
 3     private long milli;
 4 
 5     public CsCallable(String name, long milli) {
 6         this.name = name;
 7         this.milli = milli;
 8     }
 9 
10     @Override
11     public String call() throws Exception {
12         System.out.println("name:" + name);
13         Thread.sleep(milli);
14         return name + " after " + milli + "ms call back.";
15     }
16 }

ExecutorCompletionService的demo:

 1 public class CompletionServiceDemo {
 2     public static void main(String[] args) throws Exception {
 3         CompletionService completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(4));
 4         completionService.submit(new CsCallable("xxx", 5000));
 5         completionService.submit(new CsCallable("www", 2000));
 6         completionService.submit(new CsCallable("zzz", 14000));
 7         completionService.submit(new CsCallable("yyy", 9000));
 8         for (int i = 0; i < 4; i++) {
 9             System.out.println(completionService.take().get());
10         }
11         System.out.println("----- main over -----");
12     }
13 }

執行結果如下,可以看到早完成的任務結果能先獲取到

 

 

ExecutorService的demo:

 1 public class ExecutorServiceDemo {
 2     public static void main(String[] args) throws Exception {
 3         ExecutorService executorService = Executors.newFixedThreadPool(4);
 4         List<Future<String>> list = new ArrayList<>();
 5         list.add(executorService.submit(new CsCallable("xxx", 5000)));
 6         list.add(executorService.submit(new CsCallable("www", 2000)));
 7         list.add(executorService.submit(new CsCallable("zzz", 14000)));
 8         list.add(executorService.submit(new CsCallable("yyy", 9000)));
 9         for (Future<String> future : list) {
10             System.out.println(future.get());
11         }
12         System.out.println("----- main over -----");
13     }
14 }

執行結果如下,只能按照指定的順序處理返回結果,無法先處理早完成的任務

 

相關文章