Java异步编程之CompletableFuture

CompletableFuture是在 JDK1.8 才加上的类,实现了Future, CompletionStage两个接口,提供了丰富的API,配合流式(stream)编程,让异步任务处理变得简单

  • 1.CompletableFuture与Stream结合,处理异步任务
  • 2.CompletableFuture类默认使用ForkJoinPool.commonPool() ,该连接池默认使用CPU核心数为线程数,某些API可接收用户自定义线程池运行任务
  • 3.CompletableFuture提供了一些API可以组合处理不同业务类型的多个异步任务,如常用的:thenAccept、thenApply、whenComplete、thenCombine、allOf等方法

import cn.hutool.core.date.DateUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * CompletableFuture组合式异步编程
 * @description:
 * @author: zhuyu
 * @date: 2021/4/10 10:00
 */
public class CompletableFutureUtil {

    private static final int corePoolSize = Runtime.getRuntime().availableProcessors();//CPU核心数
    private static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            corePoolSize,
            corePoolSize * 2,
            1,
            TimeUnit.MINUTES,
            new ArrayBlockingQueue<>(1024),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    public static void main(String[] args) throws Exception{

        polling();//重点要理解的方法
        //runAsync();
        //supplyAsync();

        //futureComplete();
        //thenAccept();
        //thenApply();
        //whenComplete();
        //thenCombine();
        //allOf();
    }

    /**
     * 模拟网络请求,向20个Ip实例发起请求
     * 1.同步方式发起20次网络调用
     * 2.CompletableFuture方式发起20次网络调用
     */
    public static void polling(){
        List<String> ipList = new ArrayList<>();

        for(int i=1; i <= 10; i++){
            ipList.add("192.168.1." + i);
        }
        //同步调用
        long d1 = System.currentTimeMillis();
        ipList.forEach(ip -> remoteCall(ip));
        long d2 = System.currentTimeMillis();
        System.out.println("sync time : " + (d2 - d1) + " ms");

        //异步调用,使用CompletableFuture.supplyAsync异步处理
        List<CompletableFuture<String>> futures = ipList.stream()
                //将远程调用当成异步任务处理,利用ForkJoinPool.commonPool线程池
                .map(ip -> CompletableFuture.supplyAsync(() -> remoteCall(ip)))
                //对异步任务的结果进行加工处理,异步任务完成后,该线程再次对结果进行加工处理
                .map(future -> future.thenApply(CompletableFutureUtil::convertResult))
                .collect(Collectors.toList());
        //再使用future.join方法等待每个任务的结果
        List<String> results = futures.stream().map(future -> future.join()).collect(Collectors.toList());
        long d3 = System.currentTimeMillis();
        System.out.println("async time : " + (d3 - d2) + " ms");

        for(String str : results){
            System.out.println(str);
        }
    }

    /**
     * 对结果再次加工处理
     * @param result
     * @return
     */
    private static String convertResult(String result){
        System.out.println(Thread.currentThread().getName() + " convertResult : " + result);
        return "hello " + result;
    }

    /**
     * 模拟网络调用
     * @param param
     * @return
     */
    public static String remoteCall(String param){
        try {
            //模拟网络调用,处理业务,睡眠线程1s
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " " +DateUtil.now() + " reuqest param: " + param);
        }catch (Exception e){
            e.printStackTrace();
        }
        return param;
    }

    /**
     * 运行一个异步任务,无返回结果
     * @throws Exception
     */
    public static void runAsync() throws Exception {
        CompletableFuture future = CompletableFuture.runAsync(() -> remoteCall("runAsync"));
        future.join(); //同步等待任务处理完成
    }

    /**
     * 运行一个异步任务,获取任务处理结果
     * @throws Exception
     */
    public static void supplyAsync() throws Exception {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> remoteCall("supplyAsync"));
        System.out.println(DateUtil.now() +  " , response result: " + future.get());

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> remoteCall("supplyAsync") , poolExecutor);
        System.out.println(DateUtil.now() +  " , response result: " + future2.get());
    }

    /**
     * 创建一个线程,把结果设置到Future中,此方式使用较少
     * @throws Exception
     */
    public static void futureComplete() throws Exception {
        CompletableFuture<String> future = new CompletableFuture<>();
        new Thread(() -> {
            try {
                //发起网络调用
                String result = remoteCall("supplyAsync");
                //把结果设置到future的返回值中
                future.complete(result);
            }catch (Exception e){
                //设置异常
                future.completeExceptionally(e);
            }
        }).start();
        System.out.println(DateUtil.now() +  " , response result: " + future.get());
    }

    /**
     * thenAccept(Consumer<T>) 消费上一个任务的结果
     * Consumer参数视为上一个任务在完成时要调用的回调函数
     * @throws Exception
     */
    public static void thenAccept() throws Exception{
        //第一个任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> remoteCall("123"));
        //使用thenAccept消费上一个任务的结果
        CompletableFuture<Void> future2 = future.thenAccept(result -> remoteCall("321 " + result));
        future2.join(); //同步阻塞,等待任务处理完成
    }

    /**
     * 如果两个任务有依赖关系,如B依赖A的结果,可以使用thenApply
     * thenApply(Function<T, U>) 该方法接收一个Function参数,T类型是上一个结点传入,需要被消费的值,U类型是生成的,会被输出的值
     * 相当于把上一个异步任务的返回结果转换为 U
     * @throws Exception
     */
    public static void thenApply() throws Exception{
        CompletableFuture cf = CompletableFuture.supplyAsync(() -> "I'm Awesome")
                /*这里的msg就是上一个异步任务的返回结果:I'm Awesome*/
                .thenApply(msg -> String.format("%s and am Super COOL !!!", msg))
                .thenAccept(result -> System.out.printf("%s\n", result));

        CompletableFuture cf1 = CompletableFuture.supplyAsync(()->{
            return 5;
        }).thenApply((r)->{
            r = r + 1;
            return r;
        });
        System.out.println(cf1.get());
    }

    /**
     * 异步任务执行完时使用 whenComplete 执行回调方法 和捕获异常 exceptionally
     * 哪怕supplyAsync抛出了异常,whenComplete也会执行
     */
    public static void whenComplete(){
        CompletableFuture.supplyAsync(()->{
            int a = 10/0;
            return 1;
        }).whenComplete((r, e)->{
            System.out.println("whenComplete:" + r);
        }).exceptionally(e->{
            System.out.println("exceptionally:" + e);
            return 2;
        });
    }

    /**
     * 合并操作
     * 合并两个任务的结果,在对其进行统一处理,简言之,这里的回调任务需要等待两个任务都完成后再会触发
     */
    public static void thenCombine() throws Exception{
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->{
            try {
                Thread.sleep(3000);
                System.out.println("future1 invoke ...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "future1";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
            System.out.println("future2 invoke ...");
            return "future2";
        });

        CompletableFuture<String> result = future1.thenCombine(future2, (r1, r2)->{
            return "result:" + r1 + r2;
        });

        //这里的get是阻塞的,需要等上面两个任务都完成
        System.out.println(result.get());
    }


    /**
     * 对于几十个任务都完成后再来执行相应的操作
     * 那怎么集中监听所有任务执行结束与否呢? 使用allOf(CompletableFuture<?>... cfs)
     */
    public static void allOf(){
        long start = System.currentTimeMillis();
        // 结果集
        List<String> list = new ArrayList<>();

        List<Integer> taskList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // 全流式处理转换成CompletableFuture[]
        CompletableFuture[] cfs = taskList.stream()
                .map(integer -> CompletableFuture.supplyAsync(() -> calc(integer))
                        .thenApply(h->Integer.toString(h))
                        .whenComplete((s, e) -> {
                            System.out.println("任务"+s+"完成!result="+s+",异常 e="+e+","+ DateUtil.now());
                            list.add(s);
                        })
                ).toArray(CompletableFuture[]::new);

        //监听所有异步任务完成
        CompletableFuture.allOf(cfs).join();

        System.out.println("list="+list+",耗时="+(System.currentTimeMillis()-start));
    }

    public static int calc(Integer i) {
        try {
            if (i == 1) {
                Thread.sleep(3000);//任务1耗时3秒
            } else if (i == 5) {
                Thread.sleep(5000);//任务5耗时5秒
            } else {
                Thread.sleep(1000);//其它任务耗时1秒
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return i;
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值