聊聊高并发(四十二)解析java.util.concurrent各个组件(十八) 任务的批量执行和CompletionService

上一篇讲了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;

invokeAll接口返回一个Future集合,包含了所有的任务的Future对象。invokeAny这个接口输入一组任务,返回任意一个成功执行的任务的结果,其他的任务被取消。

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

正是ExecutorService在处理批量任务时必须等待全部任务都完成才能返回结果的问题,引入了CompletionService接口。CompletionService提供了一个完成队列来解决这个问题。看一下CompletionService的接口定义。可以看到它的功能分为两部分

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

ExecutorCompletionService实现了CompletionService接口。它有3个属性

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

ExecutorCompletionService默认的构造函数里使用了LinkedBlockingQueue来作阻塞队列

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

它的submit方法很简单,就是把提交的任务封装成QueueingFuture,然后交给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;
    }

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

take和poll方法也很简单,直接交给完成队列completionQueue来执行阻塞队列的操作

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

看了CompletionService的实现,来看一下如何使用它。AbstractExecutorService的doInvokeAny方法使用了CompletionService。

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


猜你喜欢

转载自blog.csdn.net/ITer_ZC/article/details/46968699