异步编程CompletableFuture(2021.12.28)

11 篇文章 0 订阅
7 篇文章 0 订阅

异步(并发)编程CompletableFuture (2021-12-28)

1.0 ExecutorServicesubmit()说明

先进行介绍为什么需要学习CompletableFuture

以往我们需要获取到线程异步执行结果都是调用的submit()方法, 其方法有3个。

Future<?> submit(Runnable task);<T> Future<T> submit(Runnable task, T result);

<T> Future<T> submit(Callable<T> task);

但是获取结果的方式很不优雅,还是需要通过阻塞(或者轮训)的方式。如何避免阻塞呢?其实就是响应回调。

Future很难直接表述多个Future 结果之间的依赖性,开发中,我们经常需要达成以下目的:将两个异步计算合并为一个(这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果)等待 Future 集合中的所有任务都完成。仅等待 Future 集合中最快结束的任务完成,并返回它的结果。

下面进行jdk1.5的Future代码复习演示:

1.1.1 Future<?> submit(Runnable task);
public void applicationTest() throws Throwable {
        long currentTimeMillis = System.currentTimeMillis();
        Future<?> submit = executor.submit(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                TimeUnit.SECONDS.sleep(3);
                System.out.println("线程名称:"+Thread.currentThread().getName());
            }
        });
        System.out.println("获取到异步结果进行下一步");
        System.out.println("这里执行逻辑耗时需要上面结果依赖"+submit.get());
        TimeUnit.SECONDS.sleep(3);
        System.out.println("结果一共耗时: "+ (System.currentTimeMillis() - currentTimeMillis));
    }
// ---------------------------
获取到异步结果进行下一步
线程名称:[my-thread-1]
这里执行逻辑耗时需要上面结果依赖null
结果一共耗时: 6025

可以看出Future<?> submit(Runnable task); 实际上返回了个null, 也就是返回空的, 调用get()方法还会阻塞到调用线程的执行,

所以如果不需要返回结果, 不建议使用使用到Future<?> submit(Runnable task); 推荐直接使用 execute()方法

1.1.2 <T> Future<T> submit(Runnable task, T result);

从方法上看, 传递一个的泛型参数过去, 返回的Future<T>里面也是个泛型, 那就可以猜测返回的应该就是我们传递过去的对象参数。

public void applicationTest() throws Throwable {
        long currentTimeMillis = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        Future<?> submit = executor.submit(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println("进行了线程休眠");
                TimeUnit.SECONDS.sleep(5);
                builder.append(66);
            }
        }, builder);
        System.out.println("获取到异步结果进行下一步");
        System.out.println("这里执行逻辑耗时需要上面结果依赖" + submit.get());
        TimeUnit.SECONDS.sleep(3);
        System.out.println("结果一共耗时: " + (System.currentTimeMillis() - currentTimeMillis));
    }
// -----------------------------------
获取到异步结果进行下一步
进行了线程休眠
这里执行逻辑耗时需要上面结果依赖66
结果一共耗时: 8029

可以看出submit.get()方法返回的就是传递过去的参数。 调用get()方法也会阻塞调用线程。

1.1.3 <T> Future<T> submit(Callable<T> task);

从方法上看, 传递的是个不同参数, 是个Callable接口。

public interface Callable<V> {V call() throws Exception;} 方法上有返回值。

public void applicationTest() throws Throwable {
        long currentTimeMillis = System.currentTimeMillis();
        Future<String> task1 = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("task1进行了5秒线程休眠");
                TimeUnit.SECONDS.sleep(5);
                return "task1Callable返回参数!";
            }
        });

        Future<String> task2 = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("task2依赖task1返回参数, 并进行了3秒线程休眠");
                TimeUnit.SECONDS.sleep(3);
                return task1.get()+"task2Callable返回参数!";
            }
        });
        System.out.println("这里执行逻辑耗时3秒");
        TimeUnit.SECONDS.sleep(3);
        String str = "耗时3秒的逻辑";
        System.out.println("任务都完成了, "+task2.get()+str);
        System.out.println("方法结束, 结果一共耗时: " + (System.currentTimeMillis() - currentTimeMillis));
    }
//---------------------------------------------
这里执行逻辑耗时3秒
task1进行了5秒线程休眠
task2依赖task1返回参数, 并进行了3秒线程休眠
任务都完成了, task1Callable返回参数!task2Callable返回参数!耗时3秒的逻辑
方法结束, 结果一共耗时: 5032

可以看出submit.get()方法返回的就是使用匿名内部实现类方法里面的返回值。我们一般使用的也是这个, 调用get()方法也会阻塞调用线程。

最终得出的是: 返回的Future<?>接口其实现类FutureTask<V> 虽然也能满足我们线程异步进行处理获取结果,

后面jdk1.8引用了CompletableFuture来进行解决。

1.1.4 CompletableFuture 实现的例子
public void applicationTest() throws Throwable {
        long currentTimeMillis = System.currentTimeMillis();
        CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("task1进行了5秒线程休眠");
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task1返回参数";
        }).thenApply((result)->{
            try {
                System.out.println("task2依赖task1返回参数, 并进行了3秒线程休眠");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return result+"task2返回参数!";
        });
        System.out.println("这里执行逻辑耗时3秒");
        TimeUnit.SECONDS.sleep(3);
        String str = "耗时3秒的逻辑";
        task.whenComplete((result,throwable)->{ // 完成任务时候回调
            System.out.println("任务都完成的回调, "+result+str);
        });
        System.out.println("方法结束, 结果一共耗时: " + (System.currentTimeMillis() - currentTimeMillis));
        task.join(); // 确保所有任务结束才结束方法
    }
// --------------------------------
这里执行逻辑耗时3秒
task1进行了5秒线程休眠
方法结束, 结果一共耗时: 3033
task2依赖task1返回参数, 并进行了3秒线程休眠
任务都完成的回调, task1返回参数task2返回参数!耗时3秒的逻辑

从结果看出来是不是快了很多, 而且代码优雅使用函数式编程。

2.0 CompletableFuture介绍

CompletableFuture 是实现了Future与CompletionStage的接口实现类、 在之前Future也可以完成异步处理任务的能力, 但是获取其结果的时候是阻塞式获取结果的, 如果获取结果的时候执行耗时比较长, 调用获取结果时候会阻塞住, 其他短耗时的异步任务都没有开启。

CompletionStage接口

Stage是阶段的意思, CompletionStage代表某个同步/异步计算的一个阶段, 或者一系列异步任务中的一个子任务。

每个CompletionStage子任务所包装的可以是一个 Function (有输入有输出, 将结果流转到下一个子任务) Runnable (无输入无输出) Consumer(有输入无输出)

多个CompletionStage构建成为一条流水线, 一个环节完成后可以将结果给下一个环节, 多个CompletionStage可以链式调用。

2.1 使用runAsync、 supplyAsync创建CompletableFuture

public void applicationTest() throws Throwable {
        CompletableFuture.runAsync(()-> System.out.println("没有返回值, 参数Runnable, 默认ForkJoinPool线程池"));
        CompletableFuture.runAsync(()-> System.out.println("没有返回值, 参数Runnable, 指定线程池"),executor);
        CompletableFuture.supplyAsync(()-> {System.out.println("有返回值, 参数Supplier, 默认ForkJoinPool线程池");return 0;});
        CompletableFuture.supplyAsync(()-> {System.out.println("有返回值, 参数Supplier, 指定线程池");return 0;},executor);
    }

其方法有2种: runAsync() || supplyAsync(Supplier<U> supplier)runAsync() || supplyAsync(Supplier<U> supplier, Executor executor) 如果不传递线程池参数, 则是使用默认: ForkJoinPool

``private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();`

2.2 设置子任务的回调钩子

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println("有返回值, 参数Supplier, 默认线程池");
            //throw new RuntimeException("123");
            return 99;
        });
        supplyAsync.whenComplete((num, throwable) -> {
            System.out.println(Thread.currentThread().getName()+"子任务完成时候回调, num上一个子任务返回结果, throwable出现异常时候参数");
        });
        supplyAsync.whenCompleteAsync((num, throwable) -> {
            System.out.println(Thread.currentThread().getName()+"子任务完成时候回调, 可能不在同一线程执行, num上一个子任务返回结果, throwable出现异常时候参数");
        });
        supplyAsync.whenCompleteAsync((num, throwable) -> {
            System.out.println(Thread.currentThread().getName()+"子任务完成时候回调, 提交给指定线程池执行, num上一个子任务返回结果, throwable出现异常时候参数");
        },executor);
        supplyAsync = supplyAsync.exceptionally(throwable -> {System.out.println("发送异常的回调, 子任务发送异常了, 返回默认结果");return 88;});
        System.out.println("阻塞的获取结果"+supplyAsync.get());
    }
// ----------------------------------------
有返回值, 参数Supplier, 默认线程池
ForkJoinPool.commonPool-worker-1子任务完成时候回调, num上一个子任务返回结果, throwable出现异常时候参数
ForkJoinPool.commonPool-worker-1子任务完成时候回调, 可能不在同一线程执行, num上一个子任务返回结果, throwable出现异常时候参数
阻塞的获取结果99
[my-thread-1]子任务完成时候回调, 提交给指定线程池执行, num上一个子任务返回结果, throwable出现异常时候参数

调用 cancel() 方法取消CompletableFuture时, 任务被视为异常完成, completeExceptionally() 方法钩子也会执行,

如果没有设置异常回调钩子情况:

(1) : 在调用 get()方法时候任务内部执行异常就会抛出执行异常、

(2) : 在调用join() 和 getNow(T) 启动任务时候任务内部执行异常就会抛出CompletionException`异常

除了使用whenComplete 、 exceptionally 外, 也可以使用 hadle() 方法效果是同理的。

3.0 CompletableFuture异步任务的串行执行

CompletableFuture的串行功能主要体现在他实现的CompletionStage方法上

如果二个异步任务需要串行, (一个任务依赖另一个任务)的执行结果, 可以通过下面方法实现

  • 转换(thenCompose
  • 组合(thenCombine
  • 消费(thenAccept
  • 运行(thenRun)。
  • 带返回的消费(thenApply

消费使用执行结果。运行则只是运行特定任务。

可见CompletableFuture的优点是:

  • 异步任务结束时,会自动回调某个对象的方法;
  • 异步任务出错时,会自动回调某个对象的方法;
  • 调用线程设置好回调后,不再关心异步任务的执行。

3.1 thenApply

thenApply方法有三个重载版本, 参数代表需要串行执行的第二个异步任务, 返回值是当前任务的。

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:"+Thread.currentThread().getName()+"有返回值, 参数Supplier, 默认线程池");
            return 99;
        });
        supplyAsync = supplyAsync.thenApply((lastTimeTask)->{
            System.out.println("线程名称:"+Thread.currentThread().getName()+lastTimeTask+"上次任务执行结果, 使用新线程执行, 然后返回当前任务结果");
            return 66;
        });
        supplyAsync = supplyAsync.thenApplyAsync((lastTimeTask)->{
            System.out.println("线程名称:"+Thread.currentThread().getName()+lastTimeTask+"上次任务执行结果, 使用新线程执行, 然后返回当前任务结果");
            return 55;
        });
        supplyAsync = supplyAsync.thenApplyAsync((lastTimeTask)->{
            System.out.println("线程名称:"+Thread.currentThread().getName()+lastTimeTask+"上次任务执行结果, 使用指定线程池新线程执行, 然后返回当前任务结果");
            return 33;
        },executor);
        System.out.println("调用线程阻塞的获取结果"+supplyAsync.get());
    }
//-----------------------
线程名称:ForkJoinPool.commonPool-worker-1有返回值, 参数Supplier, 指定线程池
线程名称:ForkJoinPool.commonPool-worker-199上次任务执行结果, 使用新线程执行, 然后返回当前任务结果
线程名称:ForkJoinPool.commonPool-worker-166上次任务执行结果, 使用新线程执行, 然后返回当前任务结果
线程名称:[my-thread-1]55上次任务执行结果, 使用指定线程池新线程执行, 然后返回当前任务结果
调用线程阻塞的获取结果33

3.2 thenRun

thenRun 方法 与 其他方法不同的是, 不关心任务的处理结果, 只要前一个任务执行完成, 就开始执行回调消费任务。

同样方法也有三个重载版本

public void applicationTest() throws Throwable {
        CompletableFuture<Void> supplyAsync = CompletableFuture.runAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "有返回值, 参数Supplier, 默认线程池");
        });
        supplyAsync = supplyAsync.thenRun(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + ", 使用任务线程执行");
        });
        supplyAsync = supplyAsync.thenRunAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 可能使用新线程执行");
        });
        supplyAsync = supplyAsync.thenRunAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + ", 使用指定线程池新线程执行");
        }, executor);
        System.out.println("调用线程阻塞的获取结果" + supplyAsync.get());
    }
// -----------------------
线程名称:ForkJoinPool.commonPool-worker-1有返回值, 参数Supplier, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-1, 使用任务线程执行
线程名称:ForkJoinPool.commonPool-worker-1 可能使用新线程执行
线程名称:[my-thread-1], 使用指定线程池新线程执行
调用线程阻塞的获取结果null

3.3 thenAccept

对thenApply 与 thenRun 方法的特点进行了折中处理

接受上一个任务的处理结果、 但是当前任务没有返回结果给下一个任务。

同样方法也有三个重载版本

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "有返回值, 参数Supplier, 默认线程池");
            return 88;
        });
        supplyAsync.thenAccept((result) -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "上次任务结果:" + result);
        });
        supplyAsync.thenAcceptAsync((result) -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "上次任务结果:" + result);
        });
        supplyAsync.thenAcceptAsync((result) -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "上次任务结果:" + result);
        },executor);
        System.out.println("调用线程阻塞的获取结果" + supplyAsync.get());
    }
// ---------------------------------
线程名称:ForkJoinPool.commonPool-worker-1有返回值, 参数Supplier, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-1上次任务结果:88
线程名称:ForkJoinPool.commonPool-worker-1上次任务结果:88
线程名称:[my-thread-1]上次任务结果:88
调用线程阻塞的获取结果88

3.4 thenCompose (转换上个任务结果返回给下一轮调度)

同样方法也有三个重载版本

方法在功能上也是针对上一个任务执行后的串行调度, (1)方法参数: 上一个任务返回的结果、 (2)返回值: 一个新的CompletableFuture

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "有返回值, 参数Supplier, 默认线程池");
            return 88;
        });
        CompletableFuture<String> stringCompletableFuture = supplyAsync.thenCompose(result -> {
            System.out.println("thenCompose线程名称:" + Thread.currentThread().getName()
                    + "上个任务的结果 " + result + ", 然后转成字符串返回一个新的CompletableFuture");
            System.out.println("----------------------------------------------------------------");
            return CompletableFuture.supplyAsync(() -> String.valueOf(result));
        });
        CompletableFuture<Double> doubleCompletableFuture = stringCompletableFuture.thenComposeAsync(result -> {
            System.out.println("thenComposeAsync线程名称:" + Thread.currentThread().getName()
                    + "上个任务的结果 " + result + ", 可能使用不同同一个线程处理, 然后转成Double返回一个新的CompletableFuture");
            System.out.println("----------------------------------------------------------------");
            return CompletableFuture.supplyAsync(() -> Double.valueOf(result));
        });
        CompletableFuture<Float> floatCompletableFuture = doubleCompletableFuture.thenComposeAsync(result -> {
            System.out.println("thenComposeAsync线程名称:" + Thread.currentThread().getName()
                    + "上个任务的结果 " + result + ", 使用指定线程池线程处理, 然后转成Float返回一个新的CompletableFuture");
            System.out.println("----------------------------------------------------------------");
            return CompletableFuture.supplyAsync(() -> Float.valueOf(result.toString()));
        }, executor);
        System.out.println("调用线程阻塞的获取floatCompletableFuture结果" + floatCompletableFuture.get());
    }
// ---------------------------------------------------------
线程名称:ForkJoinPool.commonPool-worker-1有返回值, 参数Supplier, 默认线程池
thenCompose线程名称:ForkJoinPool.commonPool-worker-1上个任务的结果 88, 然后转成字符串返回一个新的CompletableFuture
----------------------------------------------------------------
thenComposeAsync线程名称:ForkJoinPool.commonPool-worker-1上个任务的结果 88, 可能使用不同同一个线程处理, 然后转成Double返回一个新的CompletableFuture
----------------------------------------------------------------
thenComposeAsync线程名称:[my-thread-1]上个任务的结果 88.0, 使用指定线程池线程处理, 然后转成Float返回一个新的CompletableFuture
----------------------------------------------------------------
调用线程阻塞的获取floatCompletableFuture结果88.0

4.0 异步任务的合并执行 (等待另外任务完成)

如果某个异步任务同时依赖另外二个异步任务的执行结果, 这就需要对另外二个异步任务进行合并。

对于合并二个异步任务的合并可以通过CompletionStage接口的 thenCombine() 、 runAfterBoth() 、thenAcceptBoth() 三个方法来实现

这三个方法主要在于方法参数的不同。

4.1 thenCombine

方法会在二个CompletionStage任务都执行完成后, 把二个任务的执行结果一起交给thenCombine() 方法来处理。

方法参数 (需要合并的子任务CompletableFuture , BiFunction<? super T,? super U,? extends V> fn)

BiFunction使用函数式接口简写匿名内部类的实现方式, (参数1) 第一个任务的返回结果, (参数2) 第二个任务的返回结果, (返回参数) 处理完的新类型

同样方法也有三个重载版本

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "任务一号返回值, 默认线程池");
            return 88;
        });
        CompletableFuture<Map> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "任务二号返回值, 默认线程池");
            return MapUtil.builder().put("任务二号", 66).build();
        });

        CompletableFuture<Map> combine = task1.thenCombine(task2, (task1Result, task2Result) -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "task1Result" + task1Result + "," +
                    " task2Result " + task2Result + " 默认线程池");
            List<String> list = new ArrayList<>(2);
            task2Result.put("任务一号", task1Result);
            list.add(task2Result.toString());
            // 返回新类型,  list
            return list;
        });
        one.thenCombineAsync(null,null ); // 重载方法, 可能在其他线程进行处理
        one.thenCombineAsync(null, null,executor); // 重载方法, 指定线程池线程进行处理
        System.out.println("调用线程阻塞的获取floatCompletableFuture结果" + combine.get());
    }
// ---------------------------------------
线程名称:ForkJoinPool.commonPool-worker-2任务一号返回值, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-2任务二号返回值, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-2oneResult88, twoResult {任务二号=66} 默认线程池
调用线程阻塞的获取floatCompletableFuture结果[{任务二号=66, 任务一号=88}]

4.2 runAfterBoth

runAfterBoth 跟 thenCombine 不同的是合并任务的处理结果, 只针对于合并的任务都执行完毕了, 进行回调消费任务

同样方法也有三个重载版本

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "任务一号返回值, 默认线程池");
            return 88;
        });
        CompletableFuture<Map> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "任务二号返回值, 默认线程池");
            return MapUtil.builder().put("任务二号", 66).build();
        });

        CompletableFuture<Void> voidCompletableFuture = task1.runAfterBoth(task2, () -> {
            System.out.println("runAfterBoth 合并的异步任务都执行完毕了, 线程名称:" + Thread.currentThread().getName() + " 默认线程池");
        });
        //task1.runAfterBothAsync(null,null ); // 重载方法, 可能在其他线程进行处理
        //task1.runAfterBothAsync(null, null,executor); // 重载方法, 指定线程池线程进行处理
        System.out.println("调用线程阻塞的获取结果" + voidCompletableFuture.get());
    }
// ------------------------
线程名称:ForkJoinPool.commonPool-worker-1任务一号返回值, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-1任务二号返回值, 默认线程池
runAfterBoth 合并的异步任务都执行完毕了, 线程名称:ForkJoinPool.commonPool-worker-1 默认线程池
调用线程阻塞的获取结果null

4.3 thenAcceptBoth

对 thenCombine 与 runAfterBoth 方法的特点进行了折中处理

接受合并二个任务的处理结果、 但是当前任务没有返回结果给下一个任务。

同样方法也有三个重载版本

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "任务一号返回值, 默认线程池");
            return 88;
        });
        CompletableFuture<Map> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "任务二号返回值, 默认线程池");
            return MapUtil.builder().put("任务二号", 66).build();
        });

        CompletableFuture<Void> voidCompletableFuture = task1.thenAcceptBoth(task2, (task1Result, task2Result) -> {
            System.out.println("thenAcceptBoth线程名称:" + Thread.currentThread().getName() + " task1Result=" + task1Result + "," +
                    " task2Result=" + task2Result + " 没有返回值, 默认线程池");
        });
        //task1.thenAcceptBothAsync(null,null ); // 重载方法, 可能在其他线程进行处理
        //task1.thenAcceptBothAsync(null, null,executor); // 重载方法, 指定线程池线程进行处理
        System.out.println("调用线程阻塞的获取结果" + voidCompletableFuture.get());
    }
// --------------------------------
线程名称:ForkJoinPool.commonPool-worker-1任务一号返回值, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-3任务二号返回值, 默认线程池
thenAcceptBoth线程名称:ForkJoinPool.commonPool-worker-3 task1Result=88, task2Result={任务二号=66} 没有返回值, 默认线程池
调用线程阻塞的获取结果null

4.4 allOf (等待所有任务完成)

CompletionStage接口的 allOf()方法会等待所有的任务结束, 以合并所有任务。

thenCombine() 只能合并二个任务, 对于需要合并多个异步任务, 就可以使用allOf()方法

public void applicationTest() throws Throwable {
        List<CompletableFuture<Void>> tasks = new ArrayList<>(8);
        List<Integer> results = new CopyOnWriteArrayList<>(); // 使用JUC线程安全容器
        for (int i = 0; i < 8; i++) {
            int j = i;
            CompletableFuture<Void> acceptAsync = CompletableFuture.supplyAsync(() -> j)
                    .thenAcceptAsync((result) -> {
                        System.out.println("线程名称:" + Thread.currentThread().getName() + "任务返回值" + result + ", 默认线程池");
                        results.add(result);
                        TimeUnit.SECONDS.sleep(1);
                    });
            tasks.add(acceptAsync);
        }
        System.out.println("循环完毕输出: results:"+results);
        CompletableFuture<Void> allOf = CompletableFuture.allOf(tasks.toArray(new CompletableFuture[tasks.size()]));
        allOf.join();
        System.out.println("allOf.join()调用线程阻塞等待CompletableFuture.allO所有任务完毕输出: results:"+results);
    }
// ----------------------------------------
循环完毕输出: results:[]
线程名称:ForkJoinPool.commonPool-worker-2任务返回值0, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-1任务返回值1, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-3任务返回值2, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-2任务返回值3, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-1任务返回值4, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-3任务返回值5, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-2任务返回值6, 默认线程池
线程名称:ForkJoinPool.commonPool-worker-1任务返回值7, 默认线程池
allOf.join()调用线程阻塞等待CompletableFuture.allO所有任务完毕输出: results:[0, 1, 2, 3, 4, 5, 6, 7] //任务执行未必是有序的

5.0 异步任务的选择执行 (选择最快任务进行下一步)

CompletableFuture 对于异步任务的选择不是按照某种条件的, 而是按照执行速度进行选择的: 二个异步任务谁的返回结果速度快, 谁的结果就将作为下一个任务的参数输入。

对于二个异步任务的选择, 可以通过CompletionStage接口的 applyToEither() 、 runAfterEither() 、acceptEither() 三个方法来实现

这三个方法主要在于方法参数的不同。 (类似上面的介绍一样, 一个接收并返回、 一个什么都不接收、 一个接收但没有返回)

5.1 applyToEither

applyToEither()方法 比较 二个 CompletionStage 的 异步任务谁返回结果快就用谁的结果进行下一个任务的操作。

同样方法也有三个重载版本

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MICROSECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 66;
        });
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("二秒后");
            return 88;
        });
        // task1  与 task2 比较
        CompletableFuture<Integer> task3 = task1.applyToEither(task2, (fastestTaskResult) -> {
            System.out.println(Thread.currentThread().getName() + "线程名称, 最快任何结果: " + fastestTaskResult);
            return fastestTaskResult+34;
        });
    	//task1.applyToEither(null,null); // 重载方法, 可能在其他线程进行处理
        //task1.applyToEither(null, null,executor); // 重载方法, 指定线程池线程进行处理
        task3.thenAcceptAsync((result)-> System.out.println("最终结果"+result)).get();
        TimeUnit.SECONDS.sleep(5);
    }
// -----------------------
ForkJoinPool.commonPool-worker-2线程名称, 最快任何结果: 66
最终结果100
二秒后

从结果看出, 确实是选择最快的任务进行task3 处理了, 但是慢的任务线程并不会取消

5.2 runAfterEither

与 其他方法不同的是, 不关心任务的处理结果, 只要前一个任务最快的执行完成, 就开始执行回调消费任务。

同样方法也有三个重载版本

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MICROSECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("1000 MICROSECONDS后");
            return 66;
        });
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("二 SECONDS 后");
            return 88;
        });
        // task1  与 task2 比较
        CompletableFuture<Void> task3 = task1.runAfterEither(task2, () -> {
            System.out.println(Thread.currentThread().getName() + "线程名称, 最快任何结果: ");
        });
        //task1.runAfterEither(null,null); // 重载方法, 可能在其他线程进行处理
        //task1.runAfterEither(null, null,executor); // 重载方法, 指定线程池线程进行处理
        task3.get();
    }

5.3 acceptEither

对 applyToEither 与 runAfterEither 方法的特点进行了折中处理

接受合并二个任务的处理结果、 但是当前任务没有返回结果给下一个任务。

public void applicationTest() throws Throwable {
        CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 66;
        });
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MICROSECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 88;
        });
        // task1  与 task2 比较
        CompletableFuture<Void> acceptEither = task1.acceptEither(task2, (fastestTaskResult) -> {
            System.out.println(Thread.currentThread().getName() + "线程名称, 最快任何结果: " + fastestTaskResult);
        });
     	//task1.acceptEitherAsync(null,null); // 重载方法, 可能在其他线程进行处理
        //task1.acceptEitherAsync(null, null,executor); // 重载方法, 指定线程池线程进行处理
        acceptEither.get();
    }
// ForkJoinPool.commonPool-worker-3线程名称, 最快任何结果: 88

扩展:

isDone()

可以用来获取是否已经完成执行。

getNow (null)

返回已完成的结果 ,否则返回 null 。

6.0 一些使用的场景例子

6.1 实现同时进行二个异步远程RPC调用, 并将结果合并, 且调用线程去处理其他事情。

public void applicationTest() throws Throwable {
        long currentTimeMillis = System.currentTimeMillis();
        CompletableFuture<Integer> RPC1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("RPC1 需要耗时三秒");
            return 66;
        });
        CompletableFuture<Integer> RPC2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("RPC2 需要耗时二秒");
            return 88;
        });
        // 使用新线程 RPC1  与 RPC2 结果合并
        CompletableFuture<Integer> task3 = RPC1.thenCombineAsync(RPC2, (rPC1Result,rPC2Result) -> {
            return rPC1Result+rPC2Result;
        });
        System.out.println("调用线程处理自己事情需要耗时5秒");
        TimeUnit.SECONDS.sleep(5);
        Integer result = task3.exceptionally((throwable) -> {
                log.error("捕获的异常信息", throwable);
                return null; // 发生异常返回null
            }).get(); // 阻塞调用线程直到获取到结果
        System.out.println(result+" 获取到结果一共耗时: "+ (System.currentTimeMillis() - currentTimeMillis));
    }
// -------------------------
调用线程处理自己事情需要耗时5秒
RPC2 需要耗时二秒
RPC1 需要耗时三秒
154 获取到结果一共耗时: 5047

6.2 批量异步远程调用RPC, 等待所有RPC调用完成, 且调用线程去处理其他事情。

public void applicationTest() throws Throwable {
        long currentTimeMillis = System.currentTimeMillis();
        List<CompletableFuture<Integer>> Futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int j = i;
            CompletableFuture<Integer> RPC = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (j == 8) {
                    throw new RuntimeException("8出现异常");
                }
                System.out.println("RPC1 需要耗时三秒");
                return 66;
            }).exceptionally((throwable) -> {
                log.error("捕获的异常信息", throwable);
                return null; // 发生异常返回null
            });
            Futures.add(RPC);
        }
        System.out.println("调用线程处理自己事情需要耗时5秒");
        TimeUnit.SECONDS.sleep(5);
        List<Integer> results = new ArrayList<>();
        for (CompletableFuture<Integer> future : Futures) {
            results.add(future.get());
        }
        System.out.println(results + " 获取到结果一共耗时: " + (System.currentTimeMillis() - currentTimeMillis));
    }
//-------------------------------
调用线程处理自己事情需要耗时5秒
RPC1 需要耗时三秒
RPC1 需要耗时三秒
RPC1 需要耗时三秒
RPC1 需要耗时三秒
RPC1 需要耗时三秒
RPC1 需要耗时三秒
RPC1 需要耗时三秒
RPC1 需要耗时三秒
16:10:08.979 [ForkJoinPool.commonPool-worker-2] ERROR com.zhihao.demo.DemoApplicationTests - 捕获的异常信息
java.util.concurrent.CompletionException: java.lang.RuntimeException: 8出现异常
RPC1 需要耗时三秒
[66, 66, 66, 66, 66, 66, 66, 66, null, 66] 获取到结果一共耗时: 12036

6.3 实现多个异步调用, 并且存在需要上一个异步调用结果

public void applicationTest() throws Throwable {
        long currentTimeMillis = System.currentTimeMillis();
        CompletableFuture<Integer> RPC1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("RPC1 需要耗时三秒");
            return 66;
        }).thenApply((result) -> { // 上个任务结果流转到下一步

            System.out.println("RPC2 抛出异常");
            int i = 10 / 0;
            return result + 88;
        }).thenApply((result) -> { // 上个任务结果流转到下一步
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("RPC3 需要耗时5秒");
            return result + 88;
        });
        System.out.println("调用线程处理自己事情需要耗时5秒");
        TimeUnit.SECONDS.sleep(5);
        Integer result = RPC1.exceptionally((throwable) -> {
            log.error("捕获的异常信息", throwable);
            return null; // 发生异常返回null
        }).get(); // 阻塞调用线程直到获取到结果, 如果没有指定异常拦截调用get会将异步任务中的异常抛给调用方
        System.out.println(result + " 获取到结果一共耗时: " + (System.currentTimeMillis() - currentTimeMillis));
    }
// -------------------------------------
调用线程处理自己事情需要耗时5秒
RPC1 需要耗时三秒
RPC2 抛出异常
16:22:58.076 [main] ERROR com.zhihao.demo.DemoApplicationTests - 捕获的异常信息
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
null 获取到结果一共耗时: 5071

从结果可以看出一旦异步任务链中一个出现了异常, 后面的调用链都不会在执行,

将 int i = 10 / 0; 去掉后的执行结果:

调用线程处理自己事情需要耗时5秒
RPC1 需要耗时三秒
RPC2 秒响应
RPC3 需要耗时5秒
242 获取到结果一共耗时: 8030

CompletableFuture 使用到的就介绍到这。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值