CompletionService:批量执行异步任务(将CompletionService应用在实际业务当中,含CompletableFuture 方式)

CompletionService简介

CompletionService是一个接口,它定义了两个主要的方法:submittakesubmit方法用于将任务提交给服务执行,而take方法则用于获取已完成的任务结果。这个接口通常与ExecutorService一起使用,但它提供了一种不同的方式来处理任务完成的通知和结果的收集。CompletionService的一个常见实现是ExecutorCompletionService,它基于BlockingQueue实现了CompletionService接口。

ExecutorCompletionService的工作原理

ExecutorCompletionService维护了一个内部的BlockingQueue,用于存放任务完成后返回的Future对象。每当一个任务完成,它的Future会被放入队列中。这意味着我们可以按任务完成的顺序来获取结果,而不是它们被提交的顺序。

CompletionServiceCompletableFuture的区别

CompletionService更适合于批量提交异步任务,并且只关心任务的结果,而不关心任务之间的依赖关系。例如,同时从多个数据源查询数据,返回任意一个可用的结果即可。
CompletableFuture更适合于构建复杂的异步计算流程,并且可以灵活地控制任务之间的依赖关系和执行顺序。例如,根据用户输入查询商品信息,然后根据商品信息查询库存和价格,最后显示给用户。

使用CompletionService的步骤

  1. 创建线程池:首先,你需要创建一个ExecutorService实例,这可以通过Executors类的静态工厂方法来完成。
  2. 创建CompletionService:然后,使用这个ExecutorService实例来创建ExecutorCompletionService
  3. 提交任务:向CompletionService提交任务,这些任务会自动被分发到线程池中执行。
  4. 获取结果:使用take方法来获取完成的任务结果,这通常是按照任务完成的顺序来的。

实际案例分析

让我们来看一个具体的例子,这个例子展示了如何使用CompletionService来处理预算科目数据。在这个场景中,我们需要从外部API获取大量的预算科目信息,并且需要按顺序处理这些信息。(根据预算科目获取预算科目详情)

/**
 * 一次查询50条,判断hasNextPage属性是否为false, 否则继续查询
 */
private void getBudgetSubjectList(Map<String, String> authMap) {
    int pageNo = 1;
    // 创建线程池
    int availabled = Runtime.getRuntime().availableProcessors();
    ExecutorService executor = Executors.newFixedThreadPool(availabled * 2 + 1);
    CompletionService<List<BudgetItems>> cs = new ExecutorCompletionService<>(executor);
    boolean hasNextPage = true;
    while (hasNextPage) {
        log.info("获取预算科目, 第{}页", pageNo);
        Map<String, Object> map = new HashMap<>();
        map.put("pageNo", pageNo);
        map.put("pageSize", 50);
        String body = JSON.toJSONString(map);
        // 获取预算科目详情
        String resp = requestMaycurApi(authMap, body, BUDGET_SUBJECT_API);
        if (resp.contains("list") && resp.contains("hasNextPage")) {
            JSONObject data = JSON.parseObject(resp).getJSONObject("data");
            hasNextPage = data.getBoolean("hasNextPage");
            List<MaycurBudgetSubject> subjects = data.getList("list", MaycurBudgetSubject.class);
            if (subjects.isEmpty()) {
                log.error("获取预算科目失败,科目列表为空");
                hasNextPage = false;
            } else {
                // 获取预算科目详情,一次最多查50个
                cs.submit(() -> getBudgetSubjectDetailList(subjects, authMap));
            }
        } else {
            hasNextPage = false;
            log.error("获取预算科目,解析数据失败");
        }
        pageNo++;
    }
    try {
        CountDownLatch latch = new CountDownLatch(pageNo - 1); // 创建一个计数器,初始值任务数量
        for (int i = 1; i <= pageNo - 1; i++) {
            Future<List<BudgetItems>> future = cs.take();
            executor.execute(() -> {
                log.info("异步存储科目详情");
                latch.countDown(); // 计数器减1
                // 存储预算科目
                List<BudgetItems> budgetItems = future.get();
                budgetItemsService.saveOrUpdateBatch(budgetItems);
            });
        }
        latch.await(); // 等待计数器归0
    } catch (Exception e) {
        String msg = "获取预算科目失败";
        log.error(msg, e);
        throw new ServiceException(msg);
    } finally {
        executor.shutdown();
    }
}

在这个例子中,我们创建了一个固定大小的线程池,并使用它来初始化ExecutorCompletionService。对于每个页面的数据,我们都将其提交给CompletionService。当我们调用cs.take()时,我们会获得一个已经完成的任务,这样我们就可以立即处理结果,而不必等待所有任务都完成。

CompletableFuture 方式

/**
     * CompletableFuture 方式
     * 一次查询50挑,判断hasNextPage属性是否为false, 否则继续查询
     */
private void getBudgetSubjectList(Map<String, String> authMap) {
        int pageNo = 1;
        // 创建线程池
        int availabled = Runtime.getRuntime().availableProcessors();
        ExecutorService executor =
                Executors.newFixedThreadPool(availabled * 2 + 1);
        boolean hasNextPage = true;
        List<CompletableFuture<Boolean> > futures = new ArrayList<>();
        try {
            while (hasNextPage) {
                log.info("获取预算科目, 第{}页", pageNo);
                Map<String, Object> map = new HashMap<>();
                map.put("pageNo", pageNo);
                map.put("pageSize", 50);
                String body = JSON.toJSONString(map);
                // 异步获取预算科目列表
                String resp = requestMaycurApi(authMap, body, BUDGET_SUBJECT_API);
                if (resp.contains("list") && resp.contains("hasNextPage")) {
                    JSONObject data = JSON.parseObject(resp).getJSONObject("data");
                    hasNextPage = data.getBoolean("hasNextPage");
                    List<MaycurBudgetSubject> subjects = data.getList("list", MaycurBudgetSubject.class);
                    if (subjects.isEmpty()) {
                        log.error("获取预算科目失败,科目列表为空");
                        hasNextPage = false;
                    } else {
                        // 异步获取预算科目详情,一次最多查50个
                        CompletableFuture<Boolean> future = CompletableFuture
                                .supplyAsync(() -> getBudgetSubjectDetailList(subjects, authMap), executor)
                                .thenApplyAsync(list -> {
                                    log.info("保存预算科目详情, 共{}条", list.size());
                                    return budgetItemsService.saveOrUpdateBatch(list);
                                }, executor);
                        future.exceptionally(e -> {
                            log.error("同步预算科目第{}页出现异常", map.get("pageNo"), e);
                            return null;
                        });
                        futures.add(future);

                    }
                } else {
                    hasNextPage = false;
                    log.error("获取预算科目,解析数据失败");
                }
                pageNo++;
            }
        } catch (Exception e) {
            String msg = "同步预算科目失败";
            log.error(msg, e);
            throw new ServiceException(msg);
        } finally {
            log.info("关闭线程池");
            // 使用allOf方法等待所有CompletableFuture完成
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            executor.shutdown();
        }
    }

结论

CompletionService是Java并发框架中的一个强大工具,它可以极大地简化异步任务结果的管理。通过使用CompletionService,我们可以更容易地控制和组织异步操作,特别是在需要按序处理结果的时候。在处理大量数据同步时,正确地使用CompletionService可以显著提高程序的效率和可维护性。记住,合理地关闭ExecutorService是非常重要的,以确保资源的正确释放。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值