Java | 一分钟掌握异步编程 | 5 - CompletableFuture异步

作者:Mars酱

声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。

转载:欢迎转载,转载前先请联系我!

前言

继续讲,前面讲了Future实现异步,优点缺点也都有,这里讲使用CompletableFuture机制,目前为止,应该说JDK原生提供的异步方式的最优方案就是CompletableFuture了,已知的开源框架Dubbo中的RPC协议实现、常用的注册配置中心Nacos中CP协议的具体实现JRaft模块,也使用CompletableFuture。好了,名气大,不多说了。

CompletableFuture实现异步

我们还是用 Java | 一分钟掌握异步编程 | 4 - Future异步 - 掘金 (juejin.cn) 中3号桌点餐的例子,3号桌点了很多餐:红烧鱼、油焖虾,中途厨子还要上厕所,这个过程,我们用 CompletableFuture 来实现,我们把两道菜拆分成两个任务,一个内急的好厨子一定是可以双飞两道菜的,先飞一道鱼:

/**
 * @author mars酱
 */
public class BraisedFish {
    public BraisedFish() {
        System.out.println("1. 抓鱼");
    }

    public void kill() {
        System.out.println("2. 杀鱼");
    }

    public void cooking() {
        System.out.println("3. 红烧鱼制作中...");
    }
}
复制代码

再来一盘虾:

/**
 * @author mars酱
 */
public class BraisedPrawns {
    public BraisedPrawns() {
        System.out.println("1. 捉虾");
    }

    public void kill() {
        System.out.println("2. 杀虾");
    }

    public void cooking() {
        System.out.println("3. 油焖大虾制作中...");
    }
}
复制代码

厨师的动作:

/**
 * @author mars酱
 */
public class Chef {
    public Chef() {
        System.out.println("1. 米其林大厨师");
    }

    public void toilet() {
        System.out.println("厨师正在拉,请稍后...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
复制代码

做鱼的任务顺序必须是捉鱼 -> 杀鱼 -> 红烧;做虾的顺序必须是先捉后杀再油焖。最后,整体流程是先做鱼,再做虾,厨子上厕所,菜好了等着厨子上完厕所洗完手,再撒上葱花:

/**
 * @author mars酱
 */
public class NoodlesRestaurant3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1. 厨师、鱼、虾都准备到位
        Chef chef = new Chef();
        BraisedFish braisedFish = new BraisedFish();
        BraisedPrawns braisedPrawns = new BraisedPrawns();
        System.out.println("---- 准备工作完成 ----");

        // 2. 做鱼
        CompletableFuture<String> killFishTask = CompletableFuture.supplyAsync(() -> {
            braisedFish.kill();
            return ">> 鱼杀好了,准备红烧";
        });
        CompletableFuture<String> cookingFishTask = killFishTask.thenApply((result) -> {
            System.out.println(result);
            braisedFish.cooking();
            return ">> 红烧鱼作品已完成";
        });

        // 3. 做虾
        CompletableFuture<String> killPrawnsTask = CompletableFuture.supplyAsync(() -> {
            braisedPrawns.kill();
            return ">> 虾杀好了,准备油焖";
        });
        CompletableFuture<String> cookingPrawnsTask = killPrawnsTask.thenApply((result) -> {
            System.out.println(result);
            braisedPrawns.cooking();
            return ">> 油焖虾作品已完成";
        });

        // 4. 厨师拉粑粑
        CompletableFuture<String> chefToilet = CompletableFuture.supplyAsync(() -> {
            chef.toilet();
            return ">> 厨师的粑粑拉完啦!";
        });
        CompletableFuture<String> chefWashHands = chefToilet.thenApply((result) -> {
            System.out.println(result);
            return ">> 厨师洗手!";
        });

        // 5. 菜品都完成了
        CompletableFuture<String> foodCompleted = cookingFishTask.thenCombineAsync(cookingPrawnsTask, (w, s) -> w + " 和 " + s + ", 等待厨师撒葱花!");

        // 6. 等待厨师上完厕所撒葱花
        CompletableFuture<String> serving = chefWashHands.thenApply((result) -> {
            String join = foodCompleted.join();
            System.out.println(join);
            System.out.println(result);
            System.out.println(">> 厨师用擦过花的手撒葱花,干净又卫生!");
            return ">> 上菜吧~";
        });
        System.out.println(serving.get());

    }
}
复制代码

运行一下下:

很好,厨师果然正在拉,继续等待...

方法简介

以上用到了一些CompletableFutre的api,这里把常用的api简单介绍一下:

一般

supplyAsync:执行CompletableFuture任务,支持返回值

runAsync:执行CompletableFuture任务,没有返回值。

异步回调

thenRun/thenRunAsync:做完第一个任务后,再做第二个任务。

thenAccept/thenAcceptAsync:第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的

thenApply:第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。

exceptionally:某个任务执行异常时,执行的回调方法;并且有抛出异常作为参数,传递到回调方法。

whenComplete:某个任务执行完成后,执行的回调方法,无返回值;并且whenComplete方法返回的CompletableFuture的result是上个任务的结果。

handle:某个任务执行完成后,执行回调方法,并且是有返回值的;并且handle方法返回的CompletableFuture的result是回调方法执行的结果。

thenCombineAsync:两个异步任务t1、t2是并行执行,彼此无先后依赖顺序,thenCombineAsync适合将两个并行执行的异步任务的结果合并返回成一个新的future。

都完成

thenCombine / thenAcceptBoth / runAfterBoth都表示:将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务

区别在于:

  • thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
  • thenAcceptBoth: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
  • runAfterBoth 不会把执行结果当做方法入参,且没有返回值。

其中一个完成

applyToEither / acceptEither / runAfterEither 都表示:将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。区别在于:

  • applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
  • acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
  • runAfterEither: 不会把执行结果当做方法入参,且没有返回值。

所有都匹配

所有任务都执行完成后,才执行 allOf返回的CompletableFuture。如果任意一个任务异常,allOf的CompletableFuture,执行get方法,会抛出异常

任意一个匹配

任意一个任务执行完,就执行anyOf返回的CompletableFuture。如果执行的任务异常,anyOf的CompletableFuture,执行get方法,会抛出异常

thenCompose

thenCompose方法会在某个任务执行完成后,将该任务的执行结果,作为方法入参,去执行指定的方法。该方法会返回一个新的CompletableFuture实例

  • 如果该CompletableFuture实例的result不为null,则返回一个基于该result新的CompletableFuture实例;
  • 如果该CompletableFuture实例为null,然后就执行这个新任务

结尾

CompletableFuture 优秀的地方之一就是它的任务的编排。比如:先完成a,再完成b;a和b同时完成再做c,满足各种各样的业务组合方式。那么,好好利用编排功能完成工作中的难点吧。

猜你喜欢

转载自juejin.im/post/7218926523251720251