CompletableFuture使用

Future的使用具有局限性,它没法直接对多个任务进行链式、组合等处理,需要借助并发工具类才能完成,实现逻辑比较复杂。

业务代码示例:批量发券功能

public List<CouponInfoDto> batchSendCoupons(String batchSendCouponsLock,CouponsSendReqVO couponsSendReqVO) {
        log.info("batchSendCoupons esf param:::{}", JSON.toJSONString(couponsSendReqVO));
        /*.....*/
        ExecutorService executorService = ExecutorKit.newFixedThreadPool(couponInfos.size());
        List<CouponInfoDto> couponInfoDtoList = new CopyOnWriteArrayList<>();
        try {
            CompletableFuture.allOf(couponInfos.stream().map(couponInfo -> CompletableFuture.supplyAsync(() -> {
                CouponUserRecord userRecord = saveCouponUserRecord(couponInfo, couponsSendReqVO.getUid(), System.currentTimeMillis(),
                        null);
                ///异步处理券的其他业务流程 发短信 统计发券数目
                asyncCouponEventForBatch(1, couponInfo);
                CacheUtil.addByNum(CacheConstant.Coupon.COUPON_INFO_NUM_KEY + couponInfo.getCouponId(), 1);
                CacheUtil.addByNum(CacheConstant.Coupon.COUPON_USER_NUM_KEY + couponInfo.getCouponId() + "_" + couponsSendReqVO.getUid(), 1);
                log.info("领券成功 uid:{} couponId:{}", userRecord.getUid(), userRecord.getCouponId());
                return true;
            }, executorService).whenComplete((result, exception) -> {
                if (null == exception && result) {
                    //发券无异常,添加优惠券信息
                    CouponInfoDto couponInfoDto = new CouponInfoDto();
                    BeanTool.copyProperties(couponInfo, couponInfoDto);
                    couponInfoDtoList.add(couponInfoDto);
                } else {
                    log.info("领券失败 uid:{} couponId:{}", couponsSendReqVO.getUid(), couponInfo.getCouponId());
                    log.error("领券失败", exception);
                }
            })).toArray(CompletableFuture[]::new)).get(5L, TimeUnit.SECONDS);
        } catch (InterruptedException exception) {
            Thread.currentThread().interrupt();
            log.error("UserCouponImpl batchSendCoupons InterruptedException:", exception);
        } catch (Exception exception) {
            log.error("UserCouponImpl batchSendCoupons :", exception);
        }
        return couponInfoDtoList;
    }

一、简介

1.1 概述

  • CompletableFuture是对Future的扩展和增强
  • CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,完美弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力
  • CompletionStage接口定义了任务编排的方法,执行某一阶段,可以向下执行后续阶段。异步执行的,默认线程池是ForkJoinPool.commonPool(),但为了业务之间互不影响,且便于定位问题,推荐使用自定义线程池

1.2 功能

1.2.1常用关系

依赖关系

  • thenApply():把前面任务的执行结果,交给后面的Function
  • thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回

and关系

  • thenCombine():合并任务,有返回值
  • thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
  • runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)

or关系

  • applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
  • acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
  • runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)

并行执行

  • allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
  • anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture

结果处理

  • whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作
  • exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成

1.2.2 异步操作

CompletableFuture提供了四个静态方法来创建一个异步操作:

public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

  • runAsync() 以Runnable函数式接口类型为参数,没有返回结果,supplyAsync() 以Supplier函数式接口类型为参数,返回结果类型为U;Supplier接口的 get()是有返回值的(会阻塞)
  • 使用没有指定Executor的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
  • 默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,该线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置ForkJoinPool线程池的线程数)。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

异步操作

public static void main(String[] args) throws Exception {
    Runnable runnable = () -> System.out.println("无返回结果异步任务");
    CompletableFuture.runAsync(runnable);

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("有返回值的异步任务");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello World";
    });
    System.out.println(future.get());
}

获取结果(join&get)

join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join()方法抛出的是uncheck异常,不会强制开发者抛出。join()方法抛出的是经过检查的异常,ExecutionException,InterruptedException需要用户手动处理(抛出或者try catch)。

结果处理

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor);

  • Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。
  • 方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
  • 这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常

异常处理

CompletableFuture提供了三种方法来处理异常:handle(),whenComplete(),exceptionly()

handle()

whenComplete()

exceptionly()

访问成功

×

访问失败

从失败中恢复

×

对结果进行转换

×

×

成功时触发

×

失败时触发

有异步版本

√(12版本)

二、应用场景

2.1 结果转换

将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果。

thenApply

thenApply接收一个函数作为参数,使用该函数处理上一个CompletableFuture调用的结果,并返回一个具有处理结果的Future对象。

常用方法

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一次执行。");
        return "Hello ";
    }).thenApply((str) -> {
        System.out.println("这是第二次执行");
        return str + " world!";
    });
    System.out.println(completableFuture.get());
}

thenCompose

thenCompose的参数为一个返回CompletableFuture实例的函数,该函数的参数是先前计算步骤的结果。

常用方法

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一次执行。");
        return "Hello ";
    }).thenCompose(str -> CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第二次执行。");
        return str + "world";
    }));
    System.out.println(completableFuture.get());
}

thenApply thenCompose的区别

  • thenApply不同的步骤在同一个CompletableFuture中进行计算。转换的是泛型中的类型,返回的是同一个CompletableFuture
  • thenCompose不同的步骤在不同CompletableFuture中进行计算。使用上一个返回的结果在新的CompletableFuture调用中进行运算,生成一个新的CompletableFuture

2.2 结果消费

结果处理结果转换系列函数返回一个新的CompletableFuture不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。

根据对结果的处理方式不同,结果消费函数可分为如下三类:

thenAccept():对单个结果进行消费

thenAcceptBoth():对两个结果进行消费

thenRun():不关心结果,只对结果执行Action

thenAccept

观察该系列函数的参数类型可知,它们是函数式接口Consumer,这个接口只有输入,没有返回值。

常用方法

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一次执行。");
        return "Hello world.";
    }).thenAccept(System.out::println);
    completableFuture.get();
}

thenAcceptBoth

thenAcceptBoth函数的作用是,当两个CompletionStage都正常完成计算的时候,就会执行提供的action消费两个异步的结果。

常用方法

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一个CompletableFuture执行。");
        return "Hello";
    }).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第二个CompletableFuture执行。");
        return " world.";
    }), (s1, s2) -> {
        System.out.println(s1 + s2);
    });
    completableFuture.get();
}

thenRun

thenRun也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun会在上一阶段 CompletableFuture计算完成的时候执行一个Runnable,而Runnable并不使用该CompletableFuture计算的结果。

常用方法

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一个CompletableFuture执行。");
        return "Hello";
    }).thenRun(()->{
        System.out.println("thenRun 执行");
    });
    completableFuture.get();
}

2.3 结果组合

将两个线程任务返回的结果作为方法入参,并进行处理。

thenCombine

合并两个线程任务的结果,并进一步处理。

常用方法

public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor);

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一个CompletableFuture执行。");
        return "Hello";
    }).thenCombine(CompletableFuture.supplyAsync(()->{
        System.out.println("这是第二个CompletableFuture执行。");
        return " world。";
    }),(s1,s2)->{
        System.out.println("执行合并结果。");
        return s1+s2;
    });
    System.out.println(completableFuture.get());
}

2.4 任务交互

线程交互指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理

applyToEither

两个线程任务相比较,先返回执行结果的,就对该结果进行下一步的转化操作。

常用方法

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一个CompletableFuture执行。");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello";
    }).applyToEither(CompletableFuture.supplyAsync(()->{
        System.out.println("这是第二个CompletableFuture执行。");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return " world。";
    }),str->str);
    System.out.println(completableFuture.get());
}

acceptEither

两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作,无返回值。

常用方法

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一个CompletableFuture执行。");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello";
    }).acceptEither(CompletableFuture.supplyAsync(()->{
        System.out.println("这是第二个CompletableFuture执行。");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return " world。";
    }), System.out::println);
    completableFuture.get();
}

runAfterEither

两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行结果。

常用方法

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第一个CompletableFuture执行。");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello";
    }).runAfterEither(CompletableFuture.supplyAsync(() -> {
        System.out.println("这是第二个CompletableFuture执行。");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return " world。";
    }), () -> {
        System.out.println("是谁先执行结束了呢?这是一个无关线程");
    });
    completableFuture.get();
}

anyOf

anyOf() 的参数是多个给定的 CompletableFuture,当其中的任何一个完成时,方法返回这个 CompletableFuture

常用方法

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

案例展示

Random random = new Random();
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(random.nextInt(5));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "hello";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(random.nextInt(1));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "world";
});
CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2);

allOf

allOf方法用来实现多 CompletableFuture 的同时执行get或者join方法。

常用方法

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

案例展示

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("future1完成!");
        return "future1完成!";
    });
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        System.out.println("future2完成!");
        return "future2完成!";
    });
    CompletableFuture<Void> completableFuture = CompletableFuture.allOf(future1, future2);
    try {
        System.out.println(completableFuture.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

2.5 异常处理

CompletableFuture 提供了三种方法来处理它们:handle()、whenComplete() 和 exceptionly()。

点击图片可查看完整电子表格

CompletableFuture常用方法总结:

分类

方法

说明

返回值

异步执行一个线程

CompletableFuture.runAsync()

CompletableFuture.supplyAsync()

默认使用ForkJoinPoll.commonPoll线程池

推荐指定线程池

runAsync无返回值,supplyAsync有返回值

两个线程依次执行

(相同CompletableFuture)

thenApply

相同CompletableFuture,获取前一个线程的结果,转换

有入参,有返回值

thenAccept

相同CompletableFuture,获取前一个线程的结果,消费

有入参,无返回值

thenRun

相同CompletableFuture,忽略结果,执行其他逻辑

无入参,无返回值

whenComplete

获取前一个线程的结果或异常,消费

无返回值

exceptionally

前面线程异常时执行,一般跟whenComplete配合使用

有返回值

handle

相当于whenComplete+exceptionally

根据是否产生异常内部if else分支处理业务逻辑

有返回值

等待两个线程执行完毕

(不同CompletableFuture)

thenCombine

将两个CompletableFuture结果合并转换

有入参,有返回值

thenAcceptBoth

将两个CompletableFuture结果合并消费

有入参,无返回值

runAfterBoth

等待两个CompletableFuture执行结束,进行其他逻辑

无入参,无返回值

等待两个线程任一执行完毕

(不同CompletableFuture)

applyToEither

两个CompletableFuture,等待任一先结束,结果转换

有入参,有返回值

acceptToEither

两个CompletableFuture,等待任一先结束,结果消费

有入参,无返回值

runAfterEither

两个CompletableFuture,等待任一先结束,执行其他逻辑

无入参,无返回值

多个线程等待

anyOf

多个CompletableFuture任一执行完毕即返回

有返回值Object

allOf

多个CompletableFuture全部执行完毕即返回

无返回值

三、使用案例

对于烧水泡茶这个程序,一种最优的分工方案:用两个线程 T1 和 T2 来完成烧水泡茶程序,T1 负责洗水壶、烧开水、泡茶这三道工序,T2 负责洗茶壶、洗茶杯、拿茶叶三道工序,其中 T1 在执行泡茶这道工序时需要等待 T2 完成拿茶叶的工序。如图所示。

3.1 基于Future实现

public static void main(String[] args) throws Exception {
    // 创建任务T2的FutureTask
    FutureTask<String> ft2 = new FutureTask<>(new T2Task());
    // 创建任务T1的FutureTask
    FutureTask<String> ft1 = new FutureTask<>(new T1Task(ft2));
    // 线程T1执行任务ft2
    Thread T1 = new Thread(ft2);
    T1.start();
    // 线程T2执行任务ft1
    Thread T2 = new Thread(ft1);
    T2.start();
    // 等待线程T1执行结果
    System.out.println(ft1.get());
}

// T1Task需要执行的任务:洗水壶、烧开水、泡茶
static class T1Task implements Callable<String> {
    FutureTask<String> ft2;
    // T1任务需要T2任务的FutureTask
    T1Task(FutureTask<String> ft2){
        this.ft2 = ft2;
    }
    @Override
    public String call() throws Exception {
        System.out.println("T1:洗水壶...");
        TimeUnit.SECONDS.sleep(1);
        System.out.println("T1:烧开水...");
        TimeUnit.SECONDS.sleep(15);
        // 获取T2线程的茶叶
        String tf = ft2.get();
        System.out.println("T1:拿到茶叶:"+tf);
        System.out.println("T1:泡茶...");
        return "上茶:" + tf;
    }
}
// T2Task需要执行的任务: 洗茶壶、洗茶杯、拿茶叶
static class T2Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("T2:洗茶壶...");
        TimeUnit.SECONDS.sleep(1);
        System.out.println("T2:洗茶杯...");
        TimeUnit.SECONDS.sleep(2);
        System.out.println("T2:拿茶叶...");
        TimeUnit.SECONDS.sleep(1);
        return "龙井";
    }
}

3.2 基于CompletableFuture实现

public static void main(String[] args) throws Exception {
    //任务1:洗水壶->烧开水
    CompletableFuture<Void> f1 = CompletableFuture
            .runAsync(() -> {
                System.out.println("T1:洗水壶...");
                sleep(1);
                System.out.println("T1:烧开水...");
                sleep(15);
            });
    //任务2:洗茶壶->洗茶杯->拿茶叶
    CompletableFuture<String> f2 = CompletableFuture
            .supplyAsync(() -> {
                System.out.println("T2:洗茶壶...");
                sleep(1);
                System.out.println("T2:洗茶杯...");
                sleep(2);
                System.out.println("T2:拿茶叶...");
                sleep(1);
                return "龙井";
            });
    //任务3:任务1和任务2完成后执行:泡茶
    CompletableFuture<String> f3 = f1.thenCombine(f2, (t1, t2) -> {
        System.out.println("T1:拿到茶叶:" + t2);
        System.out.println("T1:泡茶...");
        return "上茶:" + t2;
    });
    //等待任务3执行结果
    System.out.println(f3.join());
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值