多线程(五) -- 并发工具(二) -- J.U.C并发包(八) -- CompletableFuture组合式异步编程

1. 业务场景:

查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间。

​假如商品详情页的每个查询,需要如下标注的时间才能完成

那么,用户需要 5.5s 后才能看到商品详情页的内容。很显然是不能接受的。

如果有多个线程同时完成这 6 步操作,也许只需要 1.5s 即可完成响应。

2. 创建异步对象

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

java 复制代码
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)

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。

  • runXxxx 都是没有返回结果的,supplyXxx 都是可以获取返回结果的
  • 可以传入自定义的线程池,否则就用默认的线程池

创建线程池对象:

java 复制代码
public static ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5,
            200,
            10,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

测试方法:

java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("main.............start.......");
    // 测试1: 创建异步对象:runAsync和supplyAsync方法
    //      runAsync无返回结果
    CompletableFuture<Void> runAsyncFuture = CompletableFuture.runAsync(() -> {
        System.out.println("runAsync当前线程:" + Thread.currentThread().getId());
        int i = 10 / 2;
        System.out.println("runAsync当前运行结果:" + i);
    }, executor);
    
    //      supplyAsync有返回结果
    CompletableFuture<Integer> supplyAsyncFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync当前线程:" + Thread.currentThread().getId());
        int i = 10 / 2;
        System.out.println("supplyAsync当前运行结果:" + i);
        return i;
    }, executor);
    Integer integer = supplyAsyncFuture.get();
    System.out.println("supplyAsync获取当前运行结果:" + integer);
    System.out.println("main.............end.......");
}

结果:

3.计算结果完成时的回调方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

java 复制代码
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)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
  • 只能执行回调方法,并返回原结果,不能对之前的结果进行任何处理后返回。
  • whenComplete 可以处理正常和异常的计算结果, exceptionally 处理异常情况。
  • whenComplete 和 whenCompleteAsync 的区别:
    • whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
    • whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
  • 方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
  • 可以看到Action的类型是BiConsumer<? super T,? super Throwable>它可以处理正常的计算结果,或者异常情况。
java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("main.............start.......");

    // 测试2: 方法成功完成后的回调(无法产生新的返回值)
    //      只能执行回调方法,并返回原结果,不能对之前的结果进行任何处理后返回
    CompletableFuture<Integer> supplyAsyncFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("supplyAsync当前线程:" + Thread.currentThread().getId());
        int i = 10 / 2;
        System.out.println("supplyAsync当前运行结果:" + i);
        return i;
    }, executor).whenComplete((result, exception) -> {
        // 虽然能得到异常信息,但是没法修改返回数据
        System.out.println("异步任务完成了...结果是" + result + ";异常是" + exception);
        // 可以感知异常,同时返回默认值
    }).exceptionally(throwable -> 10);
    Integer integer = supplyAsyncFuture.get();
    System.out.println("supplyAsync获取当前运行结果:" + integer);

    System.out.println("main.............end.......");
}

结果:

4. handle 方法

handle 是执行任务完成时对结果的处理。 handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。

java 复制代码
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Execution)

和 complete 一样,可对结果做最后的处理(可处理异常),可改变返回值。

java 复制代码
// 测试3: 方法成功完成后的回调(可以产生新的返回值)
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("当前线程:" + Thread.currentThread().getId());
    int i = 10 / 2;
    System.out.println("运行结果:" + i);
    return i;
}, executor).handle((result,thr) -> {
    if (result != null) {
        return result * 2;
    }
    if (thr != null) {
        System.out.println("异步任务成功完成了...结果是:" + result + "异常是:" + thr);
        return 0;
    }
    return 0;
});

​结果

5. 线程串行化

等待上一个操作执行完成后,执行下一个操作:

  • thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
  • thenAccept 方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
  • thenRun 方法:只要上面的任务执行完成,就开始执行 thenRun,只是处理完任务后,执行 thenRun 的后续操作,不能获取上一步的执行结果。
java 复制代码
public <U > completableFuture < U > thenApply(Function < ? super T, ? extends U > fn);
public <U > completableFuture < U > thenApplyAsync(Function < ? super T, ? extends U > fn);
public <U > CompletableFuture < U > thenApplyAsync(Function < ? super T, ? extends U > fn, Executor executor);
 
public completionStage<Void> thenAccept (consumer < ? super T > action);
public Completionstage<Void> thenAcceptAsync (Consumer < ? super T > action);
public Completionstage<Void> thenAcceptAsync (consumer < ? super T > action, Executor executor);
 
public CompletionStage<Void> thenRun (Runnable action);
public Completionstage<Void> thenRunAsync (Runnable action);
public Completionstage<Void> thenRunAsync (Runnable action, Executor executor);

// Function<? super T,? extends U>
// 	T:上一个任务返回结果的类型
// 	U:当前任务的返回值类型

带有 Async 默认是异步执行的。同之前。

java 复制代码
/**
 * 4、线程串行化
 */
// 4.1、thenApplyAsync能接收上一步的返回结果,也有返回值
CompletableFuture<String> thenApplyAsync = CompletableFuture.supplyAsync(() -> {
    System.out.println("thenApplyAsync当前线程:" + Thread.currentThread().getId());
    int i = 10 / 5;
    System.out.println("thenApplyAsync当前运行结果:" + i);
    return i;
}, executor).thenApplyAsync(res -> {
    System.out.println("thenApplyAsync任务2启动了。。。。" + res);
    return "hello" + res;
}, executor);

String thenApplyAsyncResult = thenApplyAsync.get();
System.out.println("thenApplyAsync返回结果:" + thenApplyAsyncResult);

// 4.2、thenAcceptAsync能接收上一步返回结果,但无返回值
CompletableFuture<Void> thenAcceptAsync = CompletableFuture.supplyAsync(() -> {
    System.out.println("thenAcceptAsync当前线程:" + Thread.currentThread().getId());
    int i = 10 / 5;
    System.out.println("thenAcceptAsync当前运行结果:" + i);
    return i;
}, executor).thenAcceptAsync(res -> {
    System.out.println("thenAcceptAsync任务2启动了。。。。" + res);
}, executor);

// 4.3、thenRun 不能获取得到上一步的执行结果
CompletableFuture<Void> thenRunAsync = CompletableFuture.supplyAsync(() -> {
    System.out.println("thenRunAsync当前线程:" + Thread.currentThread().getId());
    int i = 10 / 5;
    System.out.println("thenRunAsync当前运行结果:" + i);
    return i;
}, executor).thenRunAsync(() -> {
    System.out.println("thenRunAsync任务2启动了。。。。");
}, executor);

结果

6. 两任务组合-都要完成

两个任务必须都完成后,触发该任务。

  • runAfterBoth:两个future不需要获取 future的结果,只需两个 future处理完任务后, 处理该任务。
  • thenAcceptBoth:组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值。
  • thenCombine:组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值
java 复制代码
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor 

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

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);

测试:

java 复制代码
/**
 * 5、两任务组合-都要完成
 */
CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> {
    System.out.println("任务1线程:" + Thread.currentThread().getId());
    int i = 10 / 5;
    System.out.println("任务1线程结束");
    return i;
}, executor);
CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> {
    System.out.println("任务2线程:" + Thread.currentThread().getId());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("任务2线程结束");
    return "hello";
}, executor);

// 5.1、不能得到两个任务的参数,也无返回结果
future01.runAfterBothAsync(future02, () -> {
    System.out.println("runAfterBothAsync任务三开始。。。");
}, executor);

// 5.2、能得到两个任务的参数,无返回结果
future01.thenAcceptBothAsync(future02, (f1, f2) -> {
    System.out.println("thenAcceptBothAsync任务三开始。。。之前的结果" + f1 + ":" + f2);
}, executor);

// 5.3、能得到两个任务的参数,并返回结果
CompletableFuture<String> thenCombineAsync = future01.thenCombineAsync(future02, (f1, f2) -> {
    System.out.println("thenCombineAsync任务三开始。。。之前的结果" + f1 + ":" + f2);
    return f1 + ":" + f2 + "->haha";
}, executor);
System.out.println("thenCombineAsync返回结果:" + thenCombineAsync.get());

结果

java 复制代码
main......start.....
任务2线程:22
任务1线程:21
任务1线程结束
任务2线程结束
runAfterBothAsync任务三开始。。。
thenCombineAsync任务三开始。。。之前的结果2:hello
thenAcceptBothAsync任务三开始。。。之前的结果2:hello
thenCombineAsync返回结果:2:hello->haha

7. 两任务组合-只要有一个任务完成就执行第三个

当两个任务中,任意一个 future 任务完成的时候,执行任务。

  • runAfterEither 方法:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值。
  • acceptEither 方法:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值。
  • applyToEither 方法:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值。
  • thenCompose 方法:thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。
java 复制代码
public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);

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

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

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 <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
java 复制代码
/**
 * 6、两个任务只要有一个完成,我们就执行任务3
 */
CompletableFuture<Object> future001 = CompletableFuture.supplyAsync(() -> {
    System.out.println("future001任务1线程:" + Thread.currentThread().getId());
    int i = 10 / 5;
    System.out.println("future001任务1线程结束");
    return i;
}, executor);
CompletableFuture<Object> future002 = CompletableFuture.supplyAsync(() -> {
    System.out.println("任务2线程:" + Thread.currentThread().getId());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("future002任务2线程结束");
    return "hello";
}, executor);

// 6.1、runAfterEitherAsync  不感知结果,自己也无返回值
future001.runAfterEitherAsync(future002, () -> {
    System.out.println("runAfterEitherAsync任务三开始。。。");
}, executor);

// 6.2、acceptEitherAsync   感知结果,自己没有返回值
future001.acceptEitherAsync(future002, (res) -> {
    System.out.println("acceptEitherAsync任务三开始。。。" + res);
}, executor);

// 6.3、acceptEitherAsync   感知结果,自己没有返回值
CompletableFuture<String> applyToEitherAsync = future001.applyToEitherAsync(future002, (res) -> {
    System.out.println("applyToEitherAsync任务三开始。。。" + res);
    return res.toString() + "-> haha";
}, executor);
System.out.println("applyToEitherAsync返回结果:" + applyToEitherAsync.get());

结果

java 复制代码
main......start.....
任务2线程:22
future001任务1线程:21
future001任务1线程结束
runAfterEitherAsync任务三开始。。。
acceptEitherAsync任务三开始。。。2
applyToEitherAsync任务三开始。。。2
applyToEitherAsync返回结果:2-> haha
future002任务2线程结束

8. 多任务组合

java 复制代码
// 等待所有任务完成
public static completableFuture<Void> all0f(completableFuture<?>... cfs);
// 只要有一个任务完成
public static completableFuture<Obiect> anyof(completableFuture<?>... cfs);

测试allOf:

java 复制代码
/**
 * 7、多任务组合
 */
CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品的属性");
    return "黑色+256g";
}, executor);

CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(3000);
        System.out.println("查询商品的图片信息");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "hello.jpg";
}, executor);

CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品的介绍");
    return "华为";
}, executor);
CompletableFuture<Void> allOf = CompletableFuture.allOf(futureAttr, futureImg, futureDesc);
allOf.get(); //等待所有线程执行完
System.out.println("allOf获取结果:" +  futureAttr.get() + "=>" + futureImg.get() + "=>" + futureDesc.get());

结果:

java 复制代码
main......start.....
查询商品的属性
查询商品的介绍
查询商品的图片信息
allOf获取结果:黑色+256g=>hello.jpg=>华为

测试:anyOf

java 复制代码
/**
 * 7、多任务组合
 */
CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品的属性");
    return "黑色+256g";
}, executor);

CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(3000);
        System.out.println("查询商品的图片信息");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "hello.jpg";
}, executor);

CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品的介绍");
    return "华为";
}, executor);
//        CompletableFuture<Void> allOf = CompletableFuture.allOf(futureAttr, futureImg, futureDesc);
//        allOf.get(); //等待所有线程执行完
//        System.out.println("allOf获取结果:" +  futureAttr.get() + "=>" + futureImg.get() + "=>" + futureDesc.get());
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureAttr, futureImg, futureDesc);
anyOf.get();
System.out.println("anyOf获取结果:" +  futureAttr.get() + "=>" + futureImg.get() + "=>" + futureDesc.get());

结果:

java 复制代码
main......start.....
查询商品的属性
查询商品的介绍
查询商品的图片信息
anyOf获取结果:黑色+256g=>hello.jpg=>华为
相关推荐
jie188945758662 小时前
c语言------
c语言·开发语言
m0_380113843 小时前
SpringBoot创建动态定时任务的几种方式
java·spring boot·spring
Gofarlic_OMS3 小时前
SolidEdge专业许可证管理工具选型关键评估标准
java·大数据·运维·服务器·人工智能
清华都得不到的好学生3 小时前
数据结构->1.稀疏数组,2.数组队列(没有取模),3.环形队列
java·开发语言·数据结构
weyyhdke3 小时前
基于SpringBoot和PostGIS的省域“地理难抵点(最纵深处)”检索及可视化实践
java·spring boot·spring
ILYT NCTR4 小时前
【springboot】Spring 官方抛弃了 Java 8!新idea如何创建java8项目
java·spring boot·spring
weixin_425023004 小时前
PG JSONB 对应 Java 字段 + MyBatis-Plus 完整实战
java·开发语言·mybatis
不早睡不改名@4 小时前
Netty源码分析---Reactor线程模型深度解析(二)
java·网络·笔记·学习·netty
子非鱼@Itfuture4 小时前
`<T> T execute(...)` 泛型方法 VS `TaskExecutor<T>` 泛型接口对比分析
java·开发语言