CompletableFuture 异步编排介绍,附实际应用demo

异步编排API

Amdahl定律

Amdahl加速定律的基本出发点是:1.对于很多科学计算,实时性要求很高,即在此类应用中时间是个关键因素,而计算负载是固定不变的。为此在一定的计算负载下,为达到实时性可利用增加处理器数来提高计算速度;2.因为固定的计算负载是可分布在多个处理器上的,这样增加了处理器就加快了执行速度,从而达到了加速的目的。在此意义下,1967年Amdahl推导出了固定负载的加速公式。 [2]
阿姆达尔定律是计算机系统设计的重要定量原理之一,于1967年由IBM360系列机的主要设计者阿姆达尔首先提出。该定律是指:系统中对某一部件采用更快执行方式所能获得的系统性能改进程度,取决于这种执行方式被使用的频率,或所占总执行时间的比例。阿姆达尔定律实际上定义了采取增强(加速)某部分功能处理的措施后可获得的性能改进或执行时间的加速比。简单来说是通过更快的处理器来获得加速是由慢的系统组件所限制。
阿姆达尔曾致力于并行处理系统的研究。对于固定负载情况下描述并行处理效果的加速比s,阿姆达尔经过深入研究给出了如下公式:
S=1/(1-a+a/n)
其中,a为并行计算部分所占比例,n为并行处理结点个数。这样,当1-a=0时,(即没有串行,只有并行)最大加速比s=n;当a=0时(即只有串行,没有并行),最小加速比s=1;当n→∞时,极限加速比s→ 1/(1-a),这也就是加速比的上限。例如,若串行代码占整个代码的25%,则并行处理的总体性能不可能超过4。这一公式已被学术界所接受,并被称做“阿姆达尔定律”,也称为“安达尔定理”(Amdahl law)。

简言之:程序处理速度由程序中串行的部分决定,所以减少程序串行部分是提升程序运行速度的关键。

一些减少程序串行部分的方案:

  1. 减少锁的竞争:
    (1)减少锁的持有时间
    (2)降低锁的请求频率
    (3)使用带有协调机制的独占锁,这种机制允许更高的并发性

  2. 减小锁的范围(“快进快出”)
    例如使用ConcurrentHashMap等JUC包下的同步容器来减小锁的范围。

  3. 减小锁的粒度
    分解锁和分段锁是减小锁粒度的两种方案:
    同样ConcurrentHashMap实现了使用一个包含默认16个锁的数组,大约把对于锁的请求较少
    到原来的16分之1。

  4. 替代独占锁
    (1)例如ReadWriteLock
    (2)原子变量Atomic CAS无锁算法

具体业务场景

考虑以下场景:
1、获取商品的基本信息 1.5s
2、获取商品的图片信息 0.5s
3、获取商品的价格信息 1s
4、获取商品的销售属性 1s
5、获取商品详情页信息 1s

假如在同一个接口中按照以上顺序来获取商品的相关信息,至少需要5s才能完成,
对于用户来说难以忍受,如果有多个线程来处理,最多只需要1.5s以后既可完成。

使用completableFuture异步编排

JDK5新增了Future接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果。

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:
将多个异步计算的结果合并成一个
等待Future集合中的所有任务都完成
Future完成事件(即,任务完成以后触发执行动作)

  • 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
  • 它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),
    支持在计算完成以后出发一些函数或者执行一些动作。
  • 它实现了Future和CompletionStage接口
API方法一览

异步completableFuture api一览

记忆方式
  • accept 能感知上一步的执行结果
    run 不感知上一步执行结果
    apply 不仅可以感知上一步执行结果,还可以有返回值
    async是否和上一步共用一个线程,也可以传入自己的线程池对象

  • thenCombine:组合两个future,获取两个任务的返回结果,并返回当前任务的返回值
    thenAcceptBoth:组合两个future,获取两个任务的返回结果,没有返回值
    runAfterBoth:只需要等待两个任务完成后,执行新的任务

  • either 两个任务中完成一个既可

  • anyOf 等待完成一个既可
    allOf 等待全部完成

CompletableFuture 异步API应用小demo

/**
 * @Author : suitianshuang
 * @Date : 2021/9/5 16:51
 */
@Service
@Slf4j
public class ZhangSan {

    public static final ExecutorService executor = Executors.newFixedThreadPool(10);

    private ZhangSan() {
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        long startTime = System.currentTimeMillis();
        log.info("\n开始时间...{}\n", startTime);
        int i = 0;
        ZhangSan zhangSan = new ZhangSan();

        while (true) {
            i++;
            if (i > 1) {
                break;
            }
            // 张三的一天
            zhangSan.theDayOfZhangSan(i);
        }

        log.info("\n耗时:{}\n", System.currentTimeMillis() - startTime);

    }

    // 张三的钱包余额
    private int balance = 0;

    private synchronized void addBalance(int money) {
        balance = balance + money;
    }

    /**
     * 异步编排加速张三的一天
     * <p>
     * 张三早上起床刷牙、吃早饭、与此同时张三打开收音机开始听新闻,做完这些之后张三去找爸妈要钱,根据要到的钱的数目决定去做什么...
     * <p>
     * 张三听收音机的同时做了起床刷牙吃早饭,花费4000毫秒,于此同时收听了新闻,张三最后等待妈妈给钱花费了1000毫秒 总耗时 5000毫秒
     */
    public void theDayOfZhangSan(int i) throws ExecutionException, InterruptedException {

        log.info("第{}天开始了", i);

        // 串行 演示张三 起床、刷牙、吃走饭,
        CompletableFuture<Void> futureStep1 = CompletableFuture.runAsync(() -> {
            log.info("张三醒了看了一眼微信余额,还有{}元", balance);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("张三穿好衣服起床了");
        }, executor).thenRunAsync(() -> {
            log.info("张三开始刷牙");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("张三刷完牙");
        }, executor).thenRunAsync(() -> {
            log.info("张三开始吃早饭");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("张三吃完早饭");
        });

        // 听新闻、和张三起床刷牙吃早饭并行
        CompletableFuture<Void> futureStep02 = CompletableFuture.runAsync(() -> {
            log.info("张三打开收音机,开始听新闻...");
            ArrayList<String> newsList = new ArrayList<>();
            newsList.add("96金60银51铜!中国代表团连续五届残奥会金牌、奖牌双第一...");
            newsList.add("北海一拖船翻沉1人落水,附近油船工作人员“见死不救”被罚");
            newsList.add("【国足0-3不敌澳大利亚,无缘12强赛开门红】在北京时间今天凌晨举行的2022国际足联卡塔尔世界杯预选赛亚洲区最终阶段B组 #国足vs澳大利亚# 的比赛中,上半场第24分钟、第26分钟澳大利亚球员马比尔、博伊尔先后破门,国足0-2落后;下半场第70分钟杜克补射破门再丢一球。最终国足0-3不敌澳大利亚,无缘12强赛开门红。值得一提的是,#国足全场0射正# ,而本轮过后,中国队小组排名");
            try {
                for (String item : newsList) {
                    Thread.sleep(1000);
                    log.info("新闻滚动播放中,这在播放:{}...", item);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("新闻结束了");
        }, executor);

        // 模拟 张三 演示张三 起床、刷牙、吃走饭之后 和妈妈要钱
        CompletableFuture<Integer> futureGetMoneyByMom = futureStep1.thenCombineAsync(futureStep02, (f1, f2) -> {
            log.info("找妈妈要100块钱...");
            try {
                Thread.sleep((int) (Math.random() * 1000) + 1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("妈妈转账100块钱");
            return 100;
        }, executor).whenCompleteAsync((res, throwable) -> {
            if (throwable == null) {
                addBalance(res);
            } else {
                log.error("妈妈没有给钱", throwable);
            }
        }, executor).handle((a,b)->{
            b.printStackTrace();
            return 0;
        });

        // 模拟 张三 演示张三 起床、刷牙、吃走饭之后 同时和爸爸要钱
        CompletableFuture<Integer> futureGetMoneyByDad = futureStep1.thenCombineAsync(futureStep02, (f1, f2) -> {
            log.info("找爸爸要10块钱...");
            try {
                Thread.sleep((int) (Math.random() * 1000) + 1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int money = (int) (Math.random() * 100) + 1;
            log.info("爸爸转账{}块钱", money);
            return money;
        }, executor).whenCompleteAsync((res, throwable) -> {
            if (throwable == null) {
                addBalance(res);
            } else {
                log.error("爸爸没有给钱", throwable);
            }
        }).exceptionally(e->{
            e.printStackTrace();
            return 0;
        });


        // 模拟无论爸爸给钱还是妈妈给钱,张三立刻行动
        CompletableFuture<String> waitMoneyFuture = futureGetMoneyByMom.applyToEitherAsync(futureGetMoneyByDad, res -> {
            log.info("张三看了一眼微信余额,已经有{}元", balance);
            if (balance < 99) {
                log.info("准备去上网");
                return "上网";
            }
            if (balance >= 100 && balance < 200) {
                log.info("准备去蹦迪");
                return "蹦迪";
            }
            if (balance >= 200 && balance < 300) {
                log.info("去约会");
                return "去约会";
            }
            if (balance >= 1000 && balance < 10000) {
                log.info("去投资");
                return "去投资";
            }
            if (balance > 10000) {
                log.info("张三财富自由了");
                return "财富自由了";
            }

            return "继续睡觉";
        }, executor);


        String doWhat = waitMoneyFuture.get();
        log.info("张三第{}天,去{}了", i, doWhat);
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值