Java 异步编程——异步处理并发任务的执行结果(CompletionService)

CompletionService

CompletionService 介绍

CompletionService 是 Java 并发包中的一个接口,用于简化异步任务的处理。它结合了 Executor 和 BlockingQueue 的特点,允许你在执行多个异步任务时,以更方便的方式获取它们的结果。

异步任务优化 CompletionService,它是 JUC(java.util.concurrent)包中的一个接口类,默认实现类只有一个ExecutorCompletionService。

  • CompletionService 将 Executor 的任务执行能力与 BlockingQueue 的结果存储功能结合在一起。Executor 负责执行任务,而 BlockingQueue 负责保存异步任务的执行结果。

    在实现上,ExecutorCompletionService 在构造函数中会创建一个 Executor 对象和 BlockingQueue(使用的基于链表的无界队列 LinkedBlockingQueue),该 BlockingQueue 的作用是保存 Executor 执行的结果。当提交一个任务到 ExecutorCompletionService 时,首先将任务包装成 QueueingFuture,它是 FutureTask 的一个子类,然后改写 FutureTask 的 done 方法,之后把 Executor 执行的结果放入 BlockingQueue 中。

  • CompletionService 用来执行 Callable 的任务,将任务的生成与结果的处理解耦,允许用户一边提交新任务一边处理完成的任务结果。调用它的 take(阻塞)/poll(非阻塞)方法便可以获取到执行完的任务结果,最先获取到的必定是先执行完的异步任务结果

  • 相较于直接使用 FutureTask,CompletionService 完全可以避免 FutureTask 类阻塞的缺点,可更加有效地处理 Future 的返回值,也就是哪个任务先执行完,CompletionService 就先取得这个任务的返回结果进行处理。

CompletionService 在 JDK 8 中引入,相对于 Future 而言,其优势是能够尽可能快的得到执行完成的任务结果。例如有4个并发任务要执行,正常情况下通过 Future.get() 获取,通常只能按照提交的顺序获得结果,如果最后提交的最先完成的话,总执行时间会长很多。如果子线程执行完成后不需要执行其他任务,用该类意义不是很大。

CompletionService 类与方法

CompletionService 并不具备异步执行任务的能力,因此要构造 CompletionService 则需要 ExecutorService,当然还允许指定不同的 BlockingQueue 实现。

  • CompletionService 类

    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 的默认实现,提供了以下两种构造函数:

    // 默认构造函数,使用 LinkedBlockingQueue 作为结果队列
    ExecutorCompletionService(Executor executor);
    // 允许在构造时指定不同的 BlockingQueue 实现
    ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue);
    
  • 提交任务方法:

    // 提交 Callable 类型的任务
    Future<V> submit(Callable<V> task);
    // 提交 Runnable 类型的任务,并指定一个返回结果对象,当任务完成时将返回此结果。
    Future<V> submit(Runnable task, V result);
    
  • 获取 Future 结果:

    // 从结果队列中获取并移除一个已完成的任务的 Future。如果没有任务完成,则立即返回 null(非阻塞)。
    Future<V> poll();
    // 从结果队列中获取并移除一个已完成的任务的 Future,如果在指定的时间内没有任务完成,则返回 null。
    Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
    // 阻塞当前线程,直到有任务完成并返回其 Future。如果没有任务完成,线程会一直等待。
    Future<V> take() throws InterruptedException;
    

Future 与 CompletionService 示例对比

使用 Future.get()

有些任务因为执行时间较长,而在他后面的任务可能执行任务的时间较短,已经提前完成了,但是并不能得到及时的处理,只能等到前面的任务执行完成后才能处理,这样设计是非常不合理的。代码如下:

public class ThreadCompletion {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        // 依次加入三个批量任务
        List<Callable<Integer>> callables = Arrays.asList(
                () ->{
                    // 模拟耗时9秒
                    sleep(9);
                    System.out.println("Task 9 completed done.");
                    return 9;
                },
                () ->{
                    // 模拟耗时6秒
                    sleep(6);
                    System.out.println("Task 6 completed done.");
                    return 6;
                },
                () ->{
                    // 模拟耗时3秒
                    sleep(3);
                    System.out.println("Task 3 completed done.");
                    return 3;
                });

        // 提交多个任务
        List<Future<Integer>> futures = new ArrayList<>();
        for (Callable<Integer> task : callables) {
            // 提交任务并获取 Future
            Future<Integer> future = executor.submit(task);
            futures.add(future); // 将 Future 添加到列表
        }
        // 或使用 List<Future<Integer>> futures = executor.invokeAll(callables);
        futures.forEach(future -> {
            try {
                System.out.println(future.get()); // 获取执行异步任务的结果。
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
    private static void sleep(long seconds) throws InterruptedException {
        TimeUnit.SECONDS.sleep(seconds);
    }
}
/* 运行结果
Task 3 completed done.
Task 6 completed done.
Task 9 completed done.
// 获取异步任务的结果
9
6
3
*/

从结果列表中遍历的每个 Future 对象并不一定处于完成状态,这时调用 get() 方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于列表后面的但是先完成的线程就会增加了额外的等待时间。

通过运行结果可以看出,由于最先获取耗时最长的异步任务的执行结果,即使耗时最短的异步任务执行完,要获取其执行完后的结果进行处理,也必须等到耗时最长的异步任务执行完。

使用 CompletionService。

CompletionService 很好地解决了异步任务的问题,在 CompletionService 中提供了提交异步任务的方法,任务提交之后调用者不再关注 Future,而是从 BlockingQueue 中获取已经执行完成的 Future,在异步任务完成之后 Future 才会被插入阻塞队列,也就是说调用者从阻塞队列中获取的 Future 是已经完成了的异步执行任务,所以再次通过 Future 的 get 方法获取结果时,调用者所在的当前线程将不会被阻塞。

public class ThreadCompletion {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        // 定义 CompletionService 并使用 ExecutorService
        CompletionService<Integer> completionService = new ExecutorCompletionService<>(executor);
        // 设定三个任务
        List<Callable<Integer>> callables = Arrays.asList(
                () -> {
                    // 模拟耗时9秒
                    sleep(9);
                    System.out.println("Task 9 completed done.");
                    return 9;
                },
                () -> {
                    // 模拟耗时6秒
                    sleep(6);
                    System.out.println("Task 6 completed done.");
                    return 6;
                },
                () -> {
                    // 模拟耗时3秒
                    sleep(3);
                    System.out.println("Task 3 completed done.");
                    return 3;
                });
        // 提交执行多个异步任务
        callables.forEach(completionService::submit);
        // 从 completionService 中获取已完成的 Future,take 方法会阻塞
        for (int i = 0; i < callables.size(); i++) {
            try {
                // 阻塞等待并获取已完成的任务结果
                Future<Integer> future  = completionService.take();
                // 获取任务结果
                Integer integer = future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    private static void sleep(long seconds) throws InterruptedException {
        TimeUnit.SECONDS.sleep(seconds);
    }
}
/*
Task 3 completed done.
3
Task 6 completed done.
6
Task 9 completed done.
9
*/

CompletionService 的实现是维护一个保存 Future 对象的 BlockingQueue。只有当这个 Future 对象状态是结束的时候,才会加入到这个 Queue 中,take() 方法会从 Queue 中取出 Future 对象,如果 Queue 是空的,就会阻塞在那里,直到有完成的 Future 对象加入到 Queue 中。

通过运行结果可以看出,哪个异步任务先执行完就,就可以先获得其执行结果进行后序的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值