您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java——CompletableFuture详解

时间:2022-07-30 10:01:43  来源:  作者:Java非凡

平时开发过程中 RunableFutureThreadExecutorServiceCallable 这些和多线程相关的class了解和使用的也比较多,相对来说更加的熟悉和了解。使用起来也更加的得心应手。但是这些组合在一起解决多线程的问题的同时也有一些不是很满足实际开发过程中的需求。然后在JDK8引入了一个新的类 CompletableFuture 来解决之前的痛点问题。接下来了解一下 CompletableFuture 的一些基本情况以及使用和注意事项。

1 CompletableFuture概述

在JDK8之前,我们使用的JAVA多线程变成,主要是 Thread+Runnable 来完成,但是这种方式有个弊端就是没有返回值。如果想要返回值怎么办呢,大多数人就会想到 Callable + Thread 的方式来获取到返回值。如下:

public class TestCompletable {

    public static void mAIn(String[] args) throws Exception{
        FutureTask<String> task = new FutureTask((Callable<String>) () -> {
            TimeUnit.SECONDS.sleep(2);
            return UUID.randomUUID().toString();
        });
        new Thread(task).start();
        String s = task.get();
        System.out.println(s);
    }
}
复制代码

从运行上面代码可以知道当调用代码 String s = task.get(); 的时候,当前主线程是阻塞状态,另一种方式获取到返回值就是通过轮询 task.isDone() 来判断任务是否做完获取返回值。因此JDK8之前提供的异步能力有一定的局限性:

  • Runnable+Thread虽然提供了多线程的能力但是没有返回值。
  • Callable+Thread的方法提供多线程和返回值的能力但是在获取返回值的时候会阻塞主线程。

Future执行流程图.png

所以上述的情况只适合不关心返回值,只要提交的Task执行了就可以。另外的就是能够容忍等待。因此我们需要更大的异步能力为了去解决这些痛点问题。比如一下场景:

  • 两个Task计算合并为一个,这两个异步计算之间相互独立,但是两者之前又有依赖关系。
  • 对于多个Task,只要一个任务返回了结果就返回结果

等等其他的一些负载的场景, JDK8 就引入了 CompletableFuture

image-20220730150022826.png

1.1 CompletableFuture与Future的关系

通过上面的类继承关系图可以知道 CompletableFuture 实现了 Future 接口和 CompletionStage 。因此 CompletableFuture是对 Futrue的功能增强包含了Future的功能。从继承的另一个 CompletionStage 的名称来看完成阶段性的接口。这个怎么理解,这个就是下面要说的CompletableFuture本质。

1.2 CompletableFuture本质

CompletableFuture本质是什么?笔者的理解CompletableFuture相当于一个Task编排工具。为什么这么说依据如下:

  • CompletableFuture#completedFuture、CompletableFuture#whenComplete 这些方法都是对某一个阶段Task计算完成然后进行下一步的动作。将下一个一个Task和前一个Task进行编排。
  • CompletableFuture#handle 将Task串连起来

这些动作其实就是Task编排。

2 CompletableFuture使用案例

下面通过自己写的一些例子和开源项目 DLedger 中的一些例子来看一下 CompletableFuture 使用。

CompletableFuture具有Future的功能:

public class TestCompletable {

    public static void main(String[] args) throws Exception{
        FutureTask<String> futureTask = new FutureTask(() -> {
            Thread.sleep(2000);
            return UUID.randomUUID().toString();
        });
        new Thread(futureTask).start();
        CompletableFuture<String> future = CompletableFuture.completedFuture(futureTask.get());
        String uuid = future.get();
        System.out.println(uuid);

    }
}
复制代码

运行代码会发现整个过程会等待会然后打印错结果:

completableFuture1.gif

Task完成后回调:

public class TestCompletable {

    public static void main(String[] args) throws Exception{

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(3);
                System.out.println("");
                return UUID.randomUUID().toString();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return UUID.randomUUID().toString();
        });
        future.whenComplete((uuid,exception)->{
            System.out.println(uuid);
            System.out.println(Thread.currentThread().getName());
        });

        System.out.println(11111);
        System.in.read();
    }

}
复制代码

运行一下看一下结果:

completableFuture2.gif

通过结果可以看出来当完成UUID生成后,又执行了whenComplete里面的回调方法。同时还可以通过 future.get() 获取到返回值。或者就用上面的代码不用get的方式。在回调函数中就能获取到。

完成任意一个Task就开始执行回调函数:

public class TestCompletable {

    public static void main(String[] args) throws Exception{

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(3);
                return "开始生成UUID-"+UUID.randomUUID().toString();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return UUID.randomUUID().toString();
        });
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(4);

                return "开始生成UUID1-"+UUID.randomUUID().toString();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return UUID.randomUUID().toString();
        });

        CompletableFuture.anyOf(future,future1).whenComplete((uuid,ex)->{
            System.out.println(uuid);
        });

        System.out.println(11111);
        System.in.read();
    }

}
复制代码

看一下执行结果:

completableFuture3.gif

上面使用了只是一部分CompletableFuture的特性。通过对Task进行编排可以做到很多的事情。

在DLedger中:

image-20220730162811121.png

通过使用 CompletableFuture 异步化,处理请求都是通过CompletableFuture#whenCompleteAsync方法。感兴趣的可以去阅读一下源码进一步了解**CompletableFuture** 在实际项目中的使用。

3 CompletableFuture使用需要注意点

对于和多线程编程扯上关系,首先想到的就是当前的Task到底由那个Thread执行,使用的不好可能会有性能问题。首先根据CompletableFuture的方法命名可以了解到:

  • xxxx():表示该方法将继续在当前执行CompletableFuture的方法线程中执行
  • xxxxAsync():表示异步,在线程池中执行。

用例子来说明:

public class TestCompletable {

    public static void main(String[] args) throws Exception{

        CompletableFuture future = new CompletableFuture();
        future.whenComplete((item,ex)->{
            System.out.println(item);
            System.out.println(Thread.currentThread().getName());
        });
        future.complete(1111);
        TimeUnit.SECONDS.sleep(2);
    }
}
复制代码

运行结果:

image-20220730201846935.png

public class TestCompletable {

    public static void main(String[] args) throws Exception{

        CompletableFuture future = new CompletableFuture();
        future.whenCompleteAsync((item,ex)->{
            System.out.println(item);
            System.out.println(Thread.currentThread().getName());
        });
        future.whenCompleteAsync((item,ex)->{
            System.out.println(item);
            System.out.println(Thread.currentThread().getName());
        }, Executors.newFixedThreadPool(10, new ThreadFactory() {
            private AtomicInteger integer = new AtomicInteger();
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "Thread-"+integer.getAndIncrement());
            }
        }));
        future.complete(1111);
        TimeUnit.SECONDS.sleep(2);
    }
}
复制代码

运行结果:

image-20220730202220638.png

Tips: 在没有指定线程池的情况下,使用的是CompletableFuture内部的线程池。

对于性能有考虑的需要注意同步和异步的使用。

4 总结

CompletableFuture可以指定异步处理流程:

  • thenAccept()处理正常结果;
  • exceptional()处理异常结果;
  • thenApplyAsync()用于串行化另一个CompletableFuture
  • anyOf()allOf()用于并行化多个CompletableFuture

CompletableFuture执行Task的时候,是需要使用线程池还是用当前的线程去执行。这个需要根据具体的情况来定。使用的时候要尽可能的小心。


作者:蚂蚁背大象
链接:https://juejin.cn/post/7126145088299728933
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


Tags:CompletableFuture   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
异步编程与CompletableFuture
在现代软件开发中,异步编程已经成为一种常见的编程范式。它能够提高程序的性能和响应速度,使得程序在执行I/O密集型操作时能够更加高效地利用系统资源。在异步编程中,一种常见...【详细内容】
2023-12-08  Search: CompletableFuture  点击:(124)  评论:(0)  加入收藏
使用JAVA CompletableFuture实现流水线化并行处理,深度实践总结
在项目开发中,后端服务对外提供API接口一般都会关注响应时长。但是某些情况下,由于业务规划逻辑的原因,我们的接口可能会是一个聚合信息处理类的处理逻辑,比如我们从多个不同的...【详细内容】
2022-10-26  Search: CompletableFuture  点击:(407)  评论:(0)  加入收藏
奇淫巧技,CompletableFuture 异步多线程是真的优雅
一个示例回顾Future一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。JDK5新增了Future接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供...【详细内容】
2022-08-17  Search: CompletableFuture  点击:(465)  评论:(0)  加入收藏
Java——CompletableFuture详解
平时开发过程中 Runable 、Future 、 Thread 、ExecutorService、Callable 这些和多线程相关的class了解和使用的也比较多,相对来说更加的熟悉和了解。使用起来也更加的得心...【详细内容】
2022-07-30  Search: CompletableFuture  点击:(432)  评论:(0)  加入收藏
异步编程不会?我教你啊!CompletableFuture
前言以前需要异步执行一个任务时,一般是用Thread或者线程池Executor去创建。如果需要返回值,则是调用Executor.submit获取Future。但是多个线程存在依赖组合,我们又能怎么办?可...【详细内容】
2020-12-09  Search: CompletableFuture  点击:(236)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(18)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(25)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(34)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(63)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(78)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(78)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(100)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(111)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(108)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(82)  评论:(0)  加入收藏
站内最新
站内热门
站内头条