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 方法的源碼中可以確認兩點:
- 線程任務確實是由 Executor 對象執行的;
- 提交某個任務時,該任務首先將被包裝爲一個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);
}
}