上一篇讲了ExecutorService关于任务的异步执行和状态控制的部分,这篇说说关于任务批量执行的部分。ExecutorSerivce中关于批量执行的接口如下
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
AbstractExecutorService给出了invokeAll和invokeAny的默认实现。通过代码可以看到ExecutorService实现的任务批量执行的逻辑和一些问题。
invokeAll的实现如下:
1. 给每个任务创建RunnableTask结构,这个类是FutureTask的实现类,然后扔给线程池去执行execute。
2. 轮询Future集合,如果任务没有执行完成!f.done(),就调用FutureTask.get()方法,这个方法会阻塞等待直到任务完成或者抛出异常
3. 当所有任务都执行完成后才返回结果
invokeAll的问题显然易见,就是必须等待所有任务完成才返回,任务执行期间是无法获得结果的,如果有些任务耗时很长,有些任务耗时很短,那么先完成的任务也只能全部任务完成后才能返回。
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { if (tasks == null) throw new NullPointerException(); List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size()); boolean done = false; try { for (Callable<T> t : tasks) { RunnableFuture<T> f = newTaskFor(t); futures.add(f); execute(f); } for (Future<T> f : futures) { if (!f.isDone()) { try { f.get(); } catch (CancellationException ignore) { } catch (ExecutionException ignore) { } } } done = true; return futures; } finally { if (!done) for (Future<T> f : futures) f.cancel(true); } }
1. submit提交任务,返回Future,进行异步任务的状态控制
2. take, poll 这两个队列操作,前者是阻塞队列操作,后者可以快速返回,也可以限时操作
CompletionService的take, poll这两个方法就是对它的完成队列进行操作,完成的任务进入完成队列,可以被直接获取,不用等待其他任务的完成。
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; }
1. private final Executor executor; 实际执行任务的Executor
2. private final AbstractExecutorService aes; 使用它的newTaskFor方法来适配Runnable和Callable任务,统一返回RunnableFuture结构
3. private final BlockingQueue<Future<V>> completionQueue; 存放执行完成的任务的完成队列,是一个阻塞队列
内部类QueueingFuture继承了FutureTask,它的目的是重写FutureTask的done方法,将完成的任务自动放入完成队列completionQueue
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; }
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 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; }
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); }
invokeAny方法是提交一组任务,然后有一个执行成功的任务就可以返回结果,然后取消其他任务。
1. List<Future<T>> futures= new ArrayList<Future<T>>(ntasks); 创建一个Future集合来存放任务的Future
2. ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this); 创建一个ComletionService
3. futures.add(ecs.submit(it.next())); 先提交一个任务
4. 在无限循环中,先看一下任务执行结果 Future<T> f = ecs.poll();
5. 如果f != null,表示已经有任务完成,然后调用f.get去取结果,如果能取到,就直接返回结果。如果抛出异常,则继续循环
6. 如果f == null,表示任务没有完成,就再提交一个任务。如果是限时操作,就计算一下时间,判断是否超时
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { try { return doInvokeAny(tasks, false, 0); } catch (TimeoutException cannotHappen) { assert false; return null; } } private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException { if (tasks == null) throw new NullPointerException(); int ntasks = tasks.size(); if (ntasks == 0) throw new IllegalArgumentException(); List<Future<T>> futures= new ArrayList<Future<T>>(ntasks); ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this); // For efficiency, especially in executors with limited // parallelism, check to see if previously submitted tasks are // done before submitting more of them. This interleaving // plus the exception mechanics account for messiness of main // loop. try { // Record exceptions so that if we fail to obtain any // result, we can throw the last exception we got. ExecutionException ee = null; long lastTime = timed ? System.nanoTime() : 0; Iterator<? extends Callable<T>> it = tasks.iterator(); // Start one task for sure; the rest incrementally futures.add(ecs.submit(it.next())); --ntasks; int active = 1; for (;;) { Future<T> f = ecs.poll(); if (f == null) { if (ntasks > 0) { --ntasks; futures.add(ecs.submit(it.next())); ++active; } else if (active == 0) break; else if (timed) { f = ecs.poll(nanos, TimeUnit.NANOSECONDS); if (f == null) throw new TimeoutException(); long now = System.nanoTime(); nanos -= now - lastTime; lastTime = now; } else f = ecs.take(); } if (f != null) { --active; try { return f.get(); } catch (ExecutionException eex) { ee = eex; } catch (RuntimeException rex) { ee = new ExecutionException(rex); } } } if (ee == null) ee = new ExecutionException(); throw ee; } finally { for (Future<T> f : futures) f.cancel(true); } }