【Java多線程-4】CompletionService詳解


我們知道,通過 Future 和 FutureTask 可以獲得線程任務的執行結果,但它們有一定的缺陷:

  • Future:多個線程任務的執行結果,我們可以通過輪詢的方式去獲取,但普通輪詢會有被阻塞的可能,升級輪詢會非常消耗cpu。
  • FutureTask:雖然我們可以調用 done 方法,在線程任務執行結束後立即返回或做其他處理,但對批量線程任務結果的管理方面有所不足。

爲了更好地應對大量線程任務結果處理的問題,JDK提供了功能強大的 CompletionService。CompletionService是一個接口,使用創建時提供的 Executor 對象(通常是線程池)來執行任務,並在內部維護了一個阻塞隊列(QueueingFuture),當任務執行結束就把任務的執行結果的 Future 對象加入到阻塞隊列中。
該接口只有一個實現類: ExecutorCompletionService。

1 CompletionService 解析

1.1 構造方法

首先看一下 ExecutorCompletionService 的構造函數:

 	/**
     * Creates an ExecutorCompletionService using the supplied
     * executor for base task execution and a
     * {@link LinkedBlockingQueue} as a completion queue.
     *
     * @param executor the executor to use
     * @throws NullPointerException if executor is {@code null}
     */
    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>>();
    }
 
    /**
     * Creates an ExecutorCompletionService using the supplied
     * executor for base task execution and the supplied queue as its
     * completion queue.
     *
     * @param executor the executor to use
     * @param completionQueue the queue to use as the completion queue
     *        normally one dedicated for use by this service. This
     *        queue is treated as unbounded -- failed attempted
     *        {@code Queue.add} operations for completed tasks cause
     *        them not to be retrievable.
     * @throws NullPointerException if executor or completionQueue are {@code null}
     */
    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;
    }

這兩個構造方法都需要傳入一個線程池,如果不指定 completionQueue,那麼默認會使用無界的 LinkedBlockingQueue。任務執行結果的 Future 對象就是加入到 completionQueue 中。

1.2 方法

CompletionService 接口提供的方法有 5 個:

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

方法簡介如下:

  • submit(Callable task):提交線程任務,交由 Executor 對象去執行,並將結果放入阻塞隊列;
  • take():在阻塞隊列中獲取並移除一個元素,該方法是阻塞的,即獲取不到的話線程會一直阻塞;
  • poll():在阻塞隊列中獲取並移除一個元素,該方法是非阻塞的,獲取不到即返回 null ;
  • poll(long timeout, TimeUnit unit):從阻塞隊列中非阻塞地獲取並移除一個元素,在設置的超時時間內獲取不到即返回 null ;

接下來,我們重點看一下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;
 }

從submit 方法的源碼中可以確認兩點:

  1. 線程任務確實是由 Executor 對象執行的;
  2. 提交某個任務時,該任務首先將被包裝爲一個QueueingFuture。

繼續追查 QueueingFuture,可以發現:
該類重寫了 FutureTask 的done方法,當計算完成時,把Executor執行的計算結果放入BlockingQueue中,而放入結果是按任務完成順序來進行的,即先完成的任務先放入阻塞隊列。

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

由此,CompletionService 實現了生產者提交任務和消費者獲取結果的解耦,任務的完成順序由 CompletionService 來保證,消費者一定是按照任務完成的先後順序來獲取執行結果。

2 CompletionService 使用示例

下面我們使用一個小例子,領略一下 CompletionService 的便利:

import java.util.Date;
import java.util.concurrent.*;

/**
 * @author guozhengMu
 * @version 1.0
 * @date 2019/11/8 20:25
 * @description
 * @modify
 */
public class CompletionServiceTest {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        CompletionService<String> cs = new ExecutorCompletionService<>(executor);
        // 此線程池運行5個線程
        for (int i = 0; i < 5; i++) {
            final int index = i;
            cs.submit(() -> {
                String name = Thread.currentThread().getName();
                System.out.println(name + " 啓動:" + new Date());
                TimeUnit.SECONDS.sleep(10 - index * 2);
                return name;
            });
        }
        executor.shutdown();

        for (int i = 0; i < 5; i++) {
            try {
                System.out.println(cs.take().get() + " 結果:" + new Date());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

運行結果:

pool-1-thread-2 啓動:Sun Nov 10 11:34:13 CST 2019
pool-1-thread-4 啓動:Sun Nov 10 11:34:13 CST 2019
pool-1-thread-3 啓動:Sun Nov 10 11:34:13 CST 2019
pool-1-thread-5 啓動:Sun Nov 10 11:34:13 CST 2019
pool-1-thread-1 啓動:Sun Nov 10 11:34:13 CST 2019
pool-1-thread-5 結果:Sun Nov 10 11:34:15 CST 2019
pool-1-thread-4 結果:Sun Nov 10 11:34:17 CST 2019
pool-1-thread-3 結果:Sun Nov 10 11:34:19 CST 2019
pool-1-thread-2 結果:Sun Nov 10 11:34:21 CST 2019
pool-1-thread-1 結果:Sun Nov 10 11:34:23 CST 2019

通過觀察運行結果,可以看到,結果輸出的順序與線程任務執行完成的順序一致。

3 完整源碼

package java.util.concurrent;
 
public class ExecutorCompletionService<V> implements CompletionService<V> {
    // 線程池
    private final Executor executor;
    private final AbstractExecutorService aes;
    // 阻塞隊列:存放線程執行結果
    private final BlockingQueue<Future<V>> completionQueue;
 
    //內部封裝的一個用來執線程的FutureTask
    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;
    }
 
    private RunnableFuture<V> newTaskFor(Callable<V> task) {
        if (aes == null)
            return new FutureTask<V>(task);
        else
            return aes.newTaskFor(task);
    }
 
    private RunnableFuture<V> newTaskFor(Runnable task, V result) {
        if (aes == null)
            return new FutureTask<V>(task, result);
        else
            return aes.newTaskFor(task, result);
    }
 
     //構造函數,這裏一般傳入一個線程池對象executor的實現類
    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;
    }
    //提交線程任務,其實最終還是executor去提交
    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;
    }
    //提交線程任務,其實最終還是executor去提交
    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;
    }
 
    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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章