java并发编程之CompletionService

版权声明:本文为博主原创文章,欢迎转载,转载请附上原文地址。 https://blog.csdn.net/xxc1605629895/article/details/81156760

应用场景

当向Executor提交多个任务并且希望获得它们在完成之后的结果,如果用FutureTask,可以循环获取task,并调用get方法去获取task执行结果,但是如果task还未完成,获取结果的线程将阻塞直到task完成,由于不知道哪个task优先执行完毕,使用这种方式效率不会很高。在jdk5时候提出接口CompletionService,它整合了Executor和BlockingQueue的功能,可以更加方便在多个任务执行时获取到任务执行结果。

案例

  • 需求:不使用求和公式,计算从1到100000000相加的和。

  • 分析设计:需求指明不能使用求和公式,只能循环依次相加,为了提高效率,我们可以将1到100000000的数分为n段由n个task执行,执行结束后merge结果求最后的和。

  • 代码实现:

public class CompletionServiceTest {

    private static final int THREAD_NUM = 10;
    private static ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUM);

    @Test
    public void test() {
        CompletionService<Long> completionService = new ExecutorCompletionService<>(executor);
        final int groupNum = 100000000 / THREAD_NUM;
        for(int i=1; i<=THREAD_NUM; i++) {
            int start = (i - 1) * groupNum + 1, end = i * groupNum;
            completionService.submit(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    long sum = 0L;
                    for(int j = start; j <= end; j++) {
                        sum += j;
                    }
                    return sum;
                }
            });
        }

        long result = 0L;
        try {
            for(int i=1; i<=THREAD_NUM; i++) {
                result += completionService.take().get();
                System.out.println(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("the result is " + result);
    }

}

运行结果:

250000005000000
400000010000000
450000015000000
800000020000000
1250000025000000
1800000030000000
2450000035000000
3200000040000000
4050000045000000
5000000050000000
the result is 5000000050000000

由上面的运行结果,我们可以看到,先提交的任务不一定先执行完成。

实现原理

CompletionService接口

CompletionService接口定义了一系列任务提交和在任务完成后获取任务相关的Future对象的方法

相关方法定义如下:

方法 描述
Future submit(Callable task) 提交一个返回执行结果的任务,方法返回一个封装任务执行情况的Future对象
Future submit(Runnable task, V result) 提交一个Runnable的任务,方法返回一个封装任务执行情况的Future对象
Future take() throws InterruptedException 获取并移除代表下一个完成的任务的Future对象,如果当前还没有完成的任务,则等待
Future poll() 获取并移除代表下一个完成的任务的Future对象,如果当前还没有完成的任务,则返回null
Future poll(long timeout, TimeUnit unit) throws InterruptedException 获取并移除代表下一个完成的任务的Future对象,如果当前还没有完成的任务,则等待指定的时间。如果在指定的时间内任务执行完成,则返回相应的Future对象;否则,返回null

ExecutorCompletionService 类

ExecutorCompletionService 类实现了CompletionService接口,给出了具体的实现。

ExecutorCompletionService 使用 Executor 来执行任务,任务执行完成后将任务相关的 Future 对象放入队列中。
外部可以使用 take,poll 等方法来获取到执行的结果。

private final Executor executor;
private final AbstractExecutorService aes;
private final BlockingQueue<Future<V>> completionQueue;

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

submit 方法相关实现

submit 方法受限将传入的 Callable 或者 Runnable 对象封装成 RunnableFuture 对象,然后使用 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;
}

这里 executor 执行任务之前先将任务封装成了 QueueingFuture 类的实例。QueueingFuture继承自FutureTask类,覆盖了 done 方法,当任务执行完成时,将任务相关的RunnableFuture对象写入队列

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

take, poll

take 方法和 poll 方法的实现就比较简单了。直接调用队列的相应方法。

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

参考:java并发编程之CompletionService

猜你喜欢

转载自blog.csdn.net/xxc1605629895/article/details/81156760