多线程 | CompletionService异步获取并行任务执行结果,主要分两点来讨论CompletionService:
1)使用方式
2)源码分析
1)使用方式
使用Future和Callable可以获取线程执行结果,但获取方式确实阻塞的,根据添加到线程池中的线程顺序,依次获取,获取不到就阻塞。
为了解决这种情况,可以采用轮询的做法。
这里主要讨论用 CompletionService 来实现异步快速收集线程执行结果。
//初始化线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool);
for (int j = 1; j <= 5; j++) {
final int index = j;
completionService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//第三个线程睡眠等待
if (index == 3) {
java.lang.Thread.sleep(3000l);
}
return index;
}
});
}
threadPool.shutdown();
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程:"+completionService.take().get()+" 任务执行结束:");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
获取的结果是无序的,第三个睡眠线程始终最后结束,也证实了获取结果的方式是非阻塞的。如下:
线程:2 任务执行结束
线程:1 任务执行结束
线程:5 任务执行结束
线程:4 任务执行结束
线程:3 任务执行结束
2)源码分析
首先看一下 构造方法:
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>>();
}
构造法方法主要初始化了一个阻塞队列,用来存储已完成的task任务。
然后看一下 completionService.submit 方法:
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
//将我们的callable任务包装成QueueingFuture
executor.execute(new QueueingFuture(f));
return f;
}
可以看到,callable任务被包装成QueueingFuture,而 QueueingFuture是 FutureTask的子类,所以最终执行了FutureTask中的run()方法。来看一下该方法:
public void run() {
//判断执行状态,保证callable任务只被运行一次
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 {
//这里回调我们创建的callable对象中的call方法
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
//处理执行结果
set(result);
}
} finally {
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
可以看到在该 FutureTask 中执行run方法,最终回调自定义的callable中的call方法,执行结束之后,通过 set(result) 处理执行结果:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//设置执行结果v,并标记线程执行状态为:NORMAL
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
//完成执行,将执行结果添加到队列
finishCompletion();
}
}
继续跟进finishCompletion()
方法,在该方法中找到 done()
方法:
protected void done() { completionQueue.add(task); }
可以看到该方法只做了一件事情,就是将执行结束的task添加到了队列中,只要队列中有元素,我们调用take()
方法时就可以获得执行的结果。
到这里就已经清晰了,异步非阻塞获取执行结果的实现原理其实就是通过队列来实现的,FutureTask
将执行结果放到队列中,先进先出,线程执行结束的顺序就是获取结果的顺序。