任务编排:CompletableFuture从入门到精通

前言

最近遇到了一个业务场景,涉及到多数据源之间的请求的流程编排,正好看到了一篇某团介绍CompletableFuture原理和使用的技术文章,主要还是涉及使用层面。网上很多文章涉及原理的部分讲的不是特别详细且比较抽象。因为涉及到多线程的工具必须要理解原理,不然一旦遇到问题排查起来就只能凭玄学,正好借此梳理一下CompletableFuture的工作原理

背景

我们把Runnable理解为最基本的线程任务,只具备在线程下执行一段逻辑的能力。为了获取执行的返回值,创造了Callable和与其配合使用的Future。为了将任务之间进行逻辑编排,就诞生了CompletableFuture。关于如何理解任务的逻辑编排,举一个简单的例子:

打开电脑-更新系统这两个操作是有先后顺序的,但是泡茶和这两个操作没有先后顺序,是可以并行的,而开始办公必须要等待其他操作结束之后才能进行,这就形成了任务编排的执行链。

在IO密集型系统中,类似的场景有很多。因为不同数据集的查询依赖主键不同,A数据集的查询主键是B数据集的一个字段这种情况很常见,通常还需要并发查询多个数据集的数据,所以对于多线程的执行编排是有需求的。

一种解决办法是CountDownLatch,让线程执行到某个地方后进行等待,直到依赖的任务执行结束。对于一些简单的执行链是可以满足的,但是当编排逻辑复杂起来,CountDownLatch会导致代码难以维护和调试。所以诞生了CompletableFuture用来描述和维护任务之间的依赖关系以进行任务编排。在实际应用中,有以下两类场景是适合使用任务编排的:

  • 多数据源请求的流程编排

  • 非阻塞化网关等NIO场景

使用方式

创建与执行

同步方法

和FutureTask类似,CompletableFuture也通过get()方法获取执行结果。但是不同的是,CompletableFuture本身可以不承载可执行的任务(相比FutureTask则必须承载一个可执行的任务Callable),通过一个用于标记执行成功并设置返回值的函数,在使用上也更为灵活,如下:

	CompletableFuture<String> demo = new CompletableFuture<>();
	demo.complete("success");
	System.out.println(demo.get());
复制代码

执行结果:success

和Future类似,get()函数也是同步阻塞的,调用get函数后线程会阻塞直到调用complete方法标记任务已经执行成功。

除了手动触发任务的完成,也可以让创建对象的同时就标记任务完成:

	CompletableFuture<String> demo = CompletableFuture.completedFuture("success");
	System.out.println(demo.get());
复制代码

执行结果:success

异步方法

相比于同步方法,异步执行更为常见。比如下面这个例子:

        CompletableFuture<String> demo = CompletableFuture.supplyAsync(() -> {
            System.out.println("do something by thread" + Thread.currentThread().getName());
            return "success";
        });
        System.out.println(demo.get());
复制代码

执行结果:
do something by threadForkJoinPool.commonPool-worker-9
success

supplyAsync方法接收一个Supplier对象,逻辑函数交给线程池中的线程异步执行

默认会使用ForkJoinPool的公共线程池来执行代码(不推荐),当然也可以指定线程池,如下:

	ExecutorService executor = Executors.newFixedThreadPool(4);
    CompletableFuture<String> demo = CompletableFuture.supplyAsync(() -> {
        System.out.println("do something by thread" + Thread.currentThread().getName());
        return "success";
    }, executor);
    System.out.println(demo.get());
复制代码

执行结果:
do something by threadpool-1-thread-1
success

如果不需要执行结果,也可以用runAsync方法:

	CompletableFuture.runAsync(() -> {
        System.out.println("do something by thread" + Thread.currentThread().getName());
    });
复制代码

执行结果:
do something by threadForkJoinPool.commonPool-worker-9

多任务编排

多任务编排是CompletableFuture的核心,这里列举不同的场景来进行说明

一元依赖

步骤2需要依赖步骤1执行完毕才能执行,类似主线程的顺序执行,可以通过以下方式实现:

      ExecutorService executor = Executors.newFixedThreadPool(4);
      CompletableFuture<String> step1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("执行【步骤1】");
        return "【步骤1的执行结果】";
      }, executor);

      CompletableFuture<String> step2 = step1.thenApply
  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值