CompletableFuture、CompletionService使用

CompletableFuture、CompletionService使用

1. 简介

大神 Doug Lea在java8 lambda表达式基础上,开发的可以用于串行、处理异常、返回结果的接口

主要的方法

  • runAsync:由于使用的是 Runnable 函数式表达式,自然也不会获取到结果
  • supplyAsync: 想获取异步计算的返回结果需要使用 supplyAsync() 方法

这里重点说下第二种

2. supplyAsync

看一个简单例子

 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            log.info("运行在一个单独的线程当中");
            return "我有返回值";
        });

        log.info(future.get());

处理多个CompletableFuture 然后汇总结果

  1. 建立多个CompletableFuture
  2. 使用allof汇总

假设有这么个场景,根据id集合查询数据,需要分批查,则可以这样

//将ids划分为子集合
List<List<Long>> lists = Lists.partition(ids, partitionSize);

//创建多个 CompletableFuture 
List<CompletableFuture<List<User>>> completableFutures = Lists.newArrayList();
        for (List<Long> list : lists) {
            CompletableFuture<List<User>> one = CompletableFuture
                    .supplyAsync(()-> userDao.queryByIds(list);
            completableFutures.add(one);
        }
        
//汇总结果
CompletableFuture<Void> allChild = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()]));

//获取结果
CompletableFuture<List<List<ServiceSummaryPo>>> tempData = allChild.thenApply(one->{
            return completableFutures.stream().map(p->p.get()).collect(Collectors.toList());
        }).exceptionally(ex -> {       //这里是处理异常
            log.error("错误--" + ex.getMessage());
            return Lists.newArrayList();
        });
        
 List<List<User>> result = tempData.get();

anyOf

线程任一个返回就可以,场景后续补充


神器CompletionService

场景:有4个耗时任务 A、B、C、D。 每个任务的执行时间随入参变化而变化

传统方案:用ExecutorService创建线程池,提交任务到线程池

ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Future> futures = new ArrayList<Future<Integer>>();
futures.add(executorService.submit(A));
futures.add(executorService.submit(B));
futures.add(executorService.submit(C));
futures.add(executorService.submit(D));

// 遍历 Future list,通过 get() 方法获取每个 future 结果
for (Future future:futures) {
    Integer result = future.get();
    // 其他业务逻辑
}

先直入主题,用 CompletionService 实现同样的场景

ExecutorService executorService = Executors.newFixedThreadPool(4);

// ExecutorCompletionService 是 CompletionService 唯一实现类
CompletionService executorCompletionService= new ExecutorCompletionService<>(executorService );

List<Future> futures = new ArrayList<Future<Integer>>();
futures.add(executorCompletionService.submit(A));
futures.add(executorCompletionService.submit(B));
futures.add(executorCompletionService.submit(C));
futures.add(executorCompletionService.submit(D));

// 遍历 Future list,通过 get() 方法获取每个 future 结果
for (int i=0; i<futures.size(); i++) {
    Integer result = executorCompletionService.take().get();
    // 其他业务逻辑
}

两种方式在代码实现上几乎一毛一样,我们曾经说过 JDK 中不会重复造轮子,如果要造一个新轮子,必定是原有的轮子在某些场景的使用上有致命缺陷

注意

第一种方式的实现:依照提交顺序,阻塞获取结果,什么意思呢?

按照程序的执行顺序,程序在 get() 任务 A 的执行结果会阻塞在那里,导致任务 B,C,D 的后续任务没办法执行。又因为每个任务执行时间是不固定的,所以无论怎样调整将任务放到 List 的顺序,都不合适,这就是致命弊端

image-20210826102739065

新轮子自然要解决这个问题,它的设计理念就是哪个任务先执行完成,get() 方法就会获取到相应的任务结果,这么做的好处是什么呢?来看个图你就瞬间理解了

image-20210826102759444

两张图一对比,执行时长高下立判了,在当今高并发的时代,这点时间差,在吞吐量上起到的效果可能不是一点半点了

第二种方式是注册一个回调函数,谁先执行完,就通知,就先拿到

可以推广的场景

  1. 多线程批量查询sql,优化时间
  2. 多线程写Excel
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值