翻车后,大家都建议我用CompletableFuture改造下,改造完真香啊

【异步编程】CompletableFuture的complete/get方法介绍 CompletableFuture的complete和get方法介绍。 阅读详情

前言

前段时间使用了CountDownLatch来做并发流程的控制,在生产上碰到了一些问题,最终问题是解决了,但是那篇文章的评论大家让我用CompletableFuture来试一试,改造完之后,发现CompletableFuture这东西真强大,有种相见恨晚的感觉。

上篇文章

# 以为很熟悉CountDownLatch的使用了,没想到在生产环境翻车了

可以来这篇文章看一下具体的业务场景

CompletableFuture改造

我先直接分享一下我是如何使用CompletableFuture的吧

// 下载文件总数,初始化
List<Integer> resultList = new ArrayList<>(1000);
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
IntStream.range(0,1000).forEach(resultList::add);
复制代码
public List<R> sendAsyncBatch(List<P> list, Executor executor, TaskLoader<R,P> loader) {

    List<R> resultList = new CopyOnWriteArrayList<>();
    if (CollectionUtils.isNotEmpty(list)) {
        Executor finalExecutor = executor;
        // 将任务拆分分成每50个为一个任务
        CollUtil.split(list, 50)
                .forEach(tempList -> {
                    CompletableFuture[] completableFutures = tempList.stream()
                            .map(p -> CompletableFuture.supplyAsync(() -> {
                                try {
                                    return loader.load(p);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                return null;
                            }, finalExecutor)
                                    .handle((result, throwable) -> {
                                        if (Objects.nonNull(throwable)) {
                                            //log.error("async error:{}", throwable.getMessage());
                                        } else if (Objects.nonNull(result)) {
                                            //log.info("async success:{}", result);
                                        } else {
                                            //log.error("async result is null");
                                        }
                                        return result;
                                    }).whenComplete((r, ex) -> {
                                        if (Objects.nonNull(r)) {
                                            resultList.add((R) r);
                                        }
                                    })
                            ).toArray(CompletableFuture[]::new);
                    CompletableFuture.allOf(completableFutures).join();
                    System.out.println(resultList.size());
                });
    }
    return resultList;
}
复制代码
// 具体业务逻辑实现接口
@FunctionalInterface
public interface TaskLoader<T,P> {

    T load(P p) throws InterruptedException;
}
复制代码
//  自定义启动器
ExecutorService executorService = BaseThreadPoolExector.queueExecutor(new ArrayBlockingQueue<>(500));
AsyncTask<Integer, Integer> asyncTask = new AsyncTask();
// 返回结果
List<Integer> list = asyncTask.sendAsyncBatch(resultList, executorService, new TaskLoadImpl());
// 返回结果处理
复制代码

我先说一下,为什么要CountDownLatch替换掉

  1. CompletableFuture为我们提供更直观、更优美的API。
  2. 在“多个任务等待完成状态”这个应用场景,在遇到异常的情况下我们不需要去手动的抛异常,以免错误处理细节导致阻塞
  3. CompletableFuture也可以定制执行器

但是他也是有缺点的,我个人感觉他的API有点多,看的时候让人眼花。

短短十几行的代码,看到了很多API supplyAsync、handle、whenComplete、allOf

之后我们还会用到runAsync、 thenApply、thenCompose等等其他的。

什么是CompletableFuture?

异步编程,利用多线程优化性能这个核心方案得以实施的基础

他的目的也很简单,同一个CPU上执行几个松耦合的任务,充分利用CPU核数,实现最大化吞吐量,避免因为阻塞造成等待时间过长;

1. 要区分并发与并行的区别

我们还需要特别地注意这两个概念不能混淆

并发:在一个CPU上串行执行

并行:多个CPU上同时执行任务

2. Future接口

CompletableFuture主要继承了Future接口,但是他比Future接口丰富的很多

// 取消
boolean cancel(boolean mayInterruptIfRunning);

// 判断是否取消
boolean isCancelled();

//是否异步计算是否已经结束
boolean isDone();

// 获取计算结果
V get() throws InterruptedException, ExecutionException;

// 设置最长计算时间,返回计算结果
V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
复制代码

​可以看到Future接口的限性,主要是用起来不省事 举个例子:A线程执行完之后通知B线程执

ExecutorService executorService = BaseThreadPoolExector.calculateExecutor();
Future<String> futureA = executorService.submit(() -> Thread.currentThread().getName());
System.out.println(futureA.get());
if (futureA.isDone()){
    Future<String> futureB = executorService.submit(() -> Thread.currentThread().getName());
    System.out.println(futureB.get());
}
executorService.shutdown();
复制代码

这里我们就需要查询futureA.isDone()结果,然后再去执行B线程的业务

而 CompletableFuture 操作起来就便捷很多了

CompletableFuture<String> completableFuture = CompletableFuture
        .supplyAsync(() -> Thread.currentThread().getName(), executorService)
        .thenApply(s -> Thread.currentThread().getName());
System.out.println(completableFuture.get());

准备执行
计划执行
supplyAsync result pool-1-thread-1, thenApply result main
线程退出
复制代码

supplyAsync执行完成之后,再去执行thenApply

没有繁琐的手工维护线程的工作,给任务分配线程的工作也不需要我们关注;

3. 错误处理细节,避免造成阻塞

CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
new Thread(() ->{
    try {
        int kk = 10/0;
        System.out.println(kk);
    }catch (Exception ex){
        //ex.printStackTrace();
        completableFuture.completeExceptionally(ex);
    }

}).start();
try {
    System.out.println(completableFuture.get());
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}
复制代码

注意到catch里面的completeExceptionally函数了吧,

这个主要的作用就是为了抛出异常,

如果缺少了他,就会造成completableFuture.get()一直处于等待造成阻塞,

与此同时,没有为我们抛出异常信息。

所以CompletableFuture的API优美之处又要体现出来了

CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
    int kk = 10 / 0;
    return kk;
}).handle((result, throwable) -> {
    System.out.println(result);
    System.out.println(throwable.getMessage());
    return result;
}).whenComplete((result ,throwable) -> System.out.println(result));
复制代码

supplyAsync配合着 handle 和 whenComplete,将异常和结果进行处理.

handle 和 whenComplete的区别

whenComplete
public CompletableFuture<T> whenComplete(
    BiConsumer<? super T, ? super Throwable> action) {
    return uniWhenCompleteStage(null, action);
}
handle
public <U> CompletableFuture<U> handle(
    BiFunction<? super T, Throwable, ? extends U> fn) {
    return uniHandleStage(null, fn);
}
复制代码

whenComplete是BiConsumer也就是直接消费不返回值,

handle是BiFunction也就是需要返回值

了解API

欲善其功,必先利其器

我们主要从这三种关系下手去了解和使用API 涉及接口

CompletionStage<R> thenApply(fn);   
CompletionStage<R> thenApplyAsync(fn);           
CompletionStage<Void> thenAccept(consumer);      
CompletionStage<Void> thenAcceptAsync(consumer); 
CompletionStage<Void> thenRun(action);           
CompletionStage<Void> thenRunAsync(action);      
CompletionStage<R> thenCompose(fn);              
CompletionStage<R> thenComposeAsync(fn);
复制代码

thenApply函数里参数入参Function<? super T,? extends U> fn,这个接口里与 CompletionStage 相关的方法是 R apply(T t),这个方法既能接收参数也支持返回值,所以 thenApply函数出参的是CompletionStage<R>。

thenAccept类型函数入参Consumer<? super T> action是一个消费类型的,回参是CompletionStage<Void>所以thenAccept类型函数不会有返回值。

thenRun函数入参Runnable action,回参CompletionStage<Void>,所以既不能接收参数也不支持返回值。

thenCombine函数入参CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn,回参CompletableFuture<V>是支持返回值的,他的作用主要使用BiFunction处理两个阶段的结果

我们只需要注意他的入参、回参和函数后缀就能够区分出他们的不同

1. CompletableFuture中的串行化关系

CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() ->{
    //int kk = 10/0;
    return Thread.currentThread().getName() + ":小郭";
},executorService).thenApply(s -> {
    return s + "拿茶叶";
}).thenApply(a ->{
    return a + ",泡茶去";
}).handle((result, ex) ->{
    if (ex != null){
        System.out.println(ex.getMessage());
    }
    return result;
}).whenComplete((r, ex) ->{
    System.out.println(r);
});
task1.join();
复制代码

执行结果:

准备执行
计划执行
pool-1-thread-1:小郭拿茶叶,泡茶去
复制代码

可以看到,是按照之上而下的顺序去执行的supplyAsync、thenApply、thenApply 如果第二阶段任务没有拿到第一阶段的结果,他就会等待

2. CompletableFuture中的汇聚AND关系

CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() ->{
    int t = new Random().nextInt(30);
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("task1=" + t);
    return t;
});
CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() ->{
    int t = new Random().nextInt(30);
    try {
        Thread.sleep(t);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("task2=" + t);
    return t;
});
CompletableFuture<Integer> task3 = task1.thenCombineAsync(task2, Integer::sum);
task3.join();
复制代码

等待task1和task2执行完成,task再进行处理

执行结果

task1=1
task2=3
4
复制代码

3. CompletableFuture中的汇聚OR关系

CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() ->{
    int t = new Random().nextInt(5);
    try {
        Thread.sleep(t * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("task1=" + t);
    return t;
});
CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() ->{
    int t = new Random().nextInt(5);
    try {
        Thread.sleep(t * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("task2=" + t);
    return t;
});
CompletableFuture<Integer> task3 = task1.applyToEither(task2, s ->s);
task3.join();
复制代码

谁先执行完先输出谁,如果相同时间执行完,则一起数据

执行结果

我快我先来 task2=2
我快我先来 task1=2
2

我快我先来 task2=0
0
复制代码

实现List任务并行执行的方式

  1. 并行流进行操作
  2. 使用CompletableFuture发起异步请求,最后使用join等待所有异步操作结束

为了更好的发挥出CompletableFuture,需要采用定制的执行器

那这两个如何选择?

  1. 进行计算密集型,并且没有I/O操作,推荐使用Sream并行流,没必要创建更多的线程,线程过多反而是一种浪费
  2. 涉及I/O等待的操作,CompletableFuture的灵活性会更高

现在回过头看一下,我上面的改造方法,是不是就感觉清晰了许多,不足的地方大家提出来

总结

  1. 这篇文章我主要是根据大家的建议,使用了Java8的CompletableFuture 来进行了原来的业务功能改造.
  2. 在执行比较耗时的业务操作时候可以使用异步编程来提高性能,加快程序的处理速度
  3. 在处理异常机制的时候,往往是让我们很头痛的,担心线程中出现的异常没有及时捕获,造成程序的阻塞或者其他方面的影响,CompletableFuture 提供了优秀的异常管理机制。
  4. CompletableFuture 还提供了 串行、聚合、优先输出的函数,更贴切业务需求做出最好的选择。

我能想到的就这么多了,欢迎大家继续补充~

CompletableFuture用法详解 小笔记 阅读详情

相关推荐

异步调用(CompletableFuture)And Spring超时重试(Retryable)和RestTemplate模版调用方法联合使用

接到一个退费回调的需求,当三方回调通知支付平台支付成功时,需要将退费成功结果异步通知到调用方(APP),如果通知失败,需要进行4次重试,采用Http方式通知调用方接口即可。重点已经标出~ 那么接下来就是技术选型了,首先异步怎么处理,立马想到了MQ啊,异步解耦,但是感觉有点杀鸡焉用宰牛刀的感觉,最终还是决定用JDK8升级java.util.concurrent包下Future的完善版Completa...

qq_30035133的博客 3607

CompletableFuture.get()指定超时时间注意用异步线程

在某些场景,调用的方法method()其返回值是一个CompletableFuture的对象,需要使用get()方法获取其中的值T,而且要指定超时时间。如果测试get(5, TimeUnit.SECONDS)在长时间阻塞中也不会报超时异常,可能是调用的method()方法内部没有使用异步线程去执行。 如果对method()获取到的结果还要进行链式操作,如thenApply等等,并且在链式操作之后要使用get()方法并指定超时时间的话,那链式操作也要使用异步线程,如 thenApplyAsync。 如果met

hct5620的博客 6867

IPMI的SOL (Serial Over LAN) 和 UART

UART全名是Universal Asynchronous Receiver/Transmitter(通用異步接收/发送器),它是一个常用的異步串行传输协议。透过UART传输前,接收方须要先知道传输方的鲍率(Baud rate):单位时间内,讯号状态变化的次数。例如 115200 bps,表示每秒会传送115200个bits--> 因此读取率就是 1/115200=8.6微秒读取一次讯号。封包内的资料长度:通常是 8bits。

yeiris的博客 1万+

【CompletableFuture】CompletableFuture中join()和get()方法的区别

【CompletableFuture】CompletableFuture中join()和get()方法的区别 一.相同点:   join()和get()方法都是用来获取CompletableFuture异步之后的返回值 二.区别:   1.join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出,   会将异常包装成CompletionException异常 /CancellationException异常,但是本质原因还是代码内存在的真正的异常,   文档说明:.

此人很懒,什么也没有留下~ 3万+

EXCEL VBA 导出高清图片方法

EXCEL VBA 导出高清图片方法 将EXCEL表格内容另存为图片的方法有很多种,简单的比如“复制为图片格式并另存”,“打印成XPS”等等,本文着重的是利用VBA实现的方法。 该方法适合用于中大型图文表格输出为图片格式,方便传阅,在office2010及以上版本最为流畅。 具体看代码: Sub printA4() Dim rng As Range '定义截图范围变量 'm = 20...

nfser_burn7的专栏 1万+

1.深入浅出:理解三极管截止、放大和饱和状态!——参考《模拟电子技术基础》清华大学华成英主讲

幼儿园水平理解三极管截止、放大和饱和状态!书上看不懂,听课听不懂的过来! 绕不开的三极管结构 以NPN为例,晶体三极管的结构,这是很多人不想看的,但是确实是非常重要的!不看结构是理解不了工作原理的! (这样记忆:N是negative,负,代表多子为电子;P是positive,正,代表多子为空穴) 注意观察三极管的结构,有助于理解工作时的状态。 两张图结合起来看,略作解释: 1.图中空心为空穴带正...

Hardware_harder的博客 4万+

翻车后!大家都建议我用CompletableFuture改造下,改造完真香啊

异步编程,利用多线程优化性能这个核心方案得以实施的基础他的目的也很简单,同一个CPU上执行几个松耦合的任务,充分利用CPU核数,实现最大化吞吐量,避免因为阻塞造成等待时间过长;这篇文章我主要是根据大家的建议,使用了Java8的CompletableFuture 来进行了原来的业务功能改造.在执行比较耗时的业务操作时候可以使用异步编程来提高性能,加快程序的处理速度。

z1ztai的博客 638

使用FileZilla在武汉大学IGS数据分析中心下载GNSS数据

2.依次点击如下图标下载到本地并解压安装(按系统选择要下载的版本)

qq_43381998的博客 6039

浅谈CompletableFuture

到这里,你可能懵逼了,那这个CompletionStage定义了那么多方法,到底是干啥用的?异步线程又为何会回调它传入的函数接口的方法呢,BiConsumer#accept()明明不是Runnable#run()、Callable#call()呀!

smart_an的专栏 2432

系统回溯信息:遇到严重错误。已将回溯写入...PTC... raceback.log请将其发送给|技术支持|。

一、问题分析 这种问题一旦遇上了,如果忘了保存,直接心态爆炸。 该问题一般是显卡驱动的问题。ProE/Creo对显卡的基本要求是:支持Open_GL的3D显卡,分辨率为1024×768像素以上,推荐使用64位独立显卡,显存512MB以上。如若显卡性能太低,打开软件后易自动退出。 二、解决方法 <1>有一个非常简单的方法是:在config.pro文件中,改变图形配置graphics为” win32_gdi”而不是默认的“...

幕光倾城的博客 2万+

高频网络协议与编程IO相关面试基本问题和知识点整理

网络IO相关基本高频面试基本问题和知识点整合

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 173万+

Java 中 CompletableFuture 异步转同步的方式

使用 CompletableFuture.get() 方法:CompletableFuture 是 Java 8 中引入的一种异步编程工具,它可以让你在不阻塞当前线程的情况下执行异步操作。当你需要等待异步操作的结果时,可以使用 CompletableFuture.get() 方法来获取结果,但这会阻塞当前线程,直到结果可用为止。使用 CompletableFuture.join() 方法:与 CompletableFuture.get() 方法类似,CompletableFuture.join() 方法也可

鳄鱼儿 3157

基于通义千问2.5-7B-Instruct的技术深度解析

通义千问2.5-7B-Instruct是一个基于Transformer架构的大型语言模型,具有70亿参数。它通过自注意力机制和前馈神经网络处理输入序列,能够捕捉长距离依赖关系,适用于多种NLP任务。微调是指在预训练模型的基础上,使用特定任务的数据进行进一步训练,以使模型更好地适应特定任务。微调可以显著提高模型在特定任务上的性能。通义千问2.5-7B-Instruct是一个强大的语言模型,适用于多种NLP任务。通过深入理解其架构、微调方法和核心代码,可以更好地应用于实际场景。

zhangjiaofa的专栏 1567

Java future 重试,使用CompletableFuture重试逻辑

I need to submit a task in an async framework I'm working on, but I need to catch for exceptions, and retry the same task multiple times before "aborting".The code I'm working with is:int retries = 0;...

weixin_30412847的博客 1214

使用R语言中的NbClust包确定最佳聚类簇数

聚类分析是一种常用的数据分析技术,它将相似的数据点分组成不同的簇。NbClust包是R语言中一个强大的工具,它提供了多种方法来帮助我们确定最佳的聚类簇数。综上所述,使用R语言中的NbClust包可以帮助我们确定最佳的聚类簇数。通过选择适当的聚类算法和评估指标,我们可以更好地理解数据集的结构和模式,并根据需要进行进一步的分析和解释。此外,NbClust包还提供了其他函数和参数,用于执行不同的聚类算法、选择不同的评估指标等。安装完毕后,我们需要加载NbClust包,以便使用其中的函数。函数来确定最佳聚类簇数。

2301_79326559的博客 2278

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

手把手教你使用 CompletableFuture

背景 在jdk5中,我们通过使用Future和Callable,可以在任务执行完毕后得到任务执行结果。可以使用isDone检测计算是否完成,使用cancle停止执行任务,使用阻塞方法get阻塞住调用线程来获取返回结果,使用阻塞方式获取执行结果,有违异步编程的初衷,而且Future的异常只能自己内部处理。 jdk8中加入了实现类CompletableFuture<T>,用于异步编程。底层做任务使用的是ForkJoin, 顾名思义,是将任务的数据集分为多个子数据集,而每个子集,都可以由独立的子任

qq_33642970的博客 3470

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

CompletableFuture常用方法

CompletableFuture常用方法

Leighteen 1404

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Java并发魔法:CompletableFuture 全解析 CompletableFuture类提供了join()和get() 看这一篇就够了

在Java的世界里,是异步编程的利器,它为开发者提供了强大的异步处理能力。今天,就让我们一起揭开的神秘面纱,深入了解它的使用方法、运行原理、应用场景以及源码分析。

赵KK的博客 5757

记一次生产中使用CompletableFuture遇到的坑

记一次生产中使用CompletableFuture遇到的坑

m0_46995061的博客 3364

CompletableFuture获取执行结果的方法

使用 CompletableFuture 中获取执行结果的方法时应该避免阻塞主线程,因为它们都可能会导致线程阻塞,影响程序的整体性能和响应能力。因此,在实际使用中我们应该结合 CompletableFuture 的回调函数或者连续的异步操作等方法,来实现非阻塞的异步计算和结果处理。

Ascend1977的博客 9509

CompletableFuture真香,可以替代CountDownLatch!

文章来源:【公众号:小姐姐味道 (xjjdog)】目录背景一系列静态方法这些花样有很多when 和 handle替代 CountDownLatch总结背景之前我们提到了 Future 和 Promise。Future 相当于一个占位符,代表一个操作将来的结果。一般通过 get 可以直接阻塞得到结果,或者让它异步执行然后通过 callback 回调结果。但如果回调中嵌入了回...

石杉的架构笔记 2078

使用CompletableFuture进行异步处理业务

CompletableFuture进行异步处理业务

不知名博客 709

CompletableFuture中get和join对比

CompletableFuture中get和join对比

weixin_42202992的博客 1459

future java get_一文秒懂 Java CompletableFuture ( 上 )

本文我们来了解下 Java 8 引入的 CompletableFuture 类,了解下该类提供的功能和用例。Java 中的异步计算异步计算很难推理的,因为我们的大脑是同步的,会将任何计算看成是一系列的同步计算。我们在实现异步计算时,往往会把回调的动作分散在代码中或者深深地嵌套在彼此内部,这种情况下,当我们需要处理其中一个步骤中可能发生的错误时,情况变得更糟。人生的一大悲剧是,尽管 Java 5 已...

weixin_29818013的博客 760

CompletableFuture使用详解

一、简介 1.1 概述 在上一篇文章《CompletionService使用与源码分析》中,已经介绍过了Future的局限性,它没法直接对多个任务进行链式、组合等处理,需要借助并发工具类才能完成,实现逻辑比较复杂。 而CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,完美弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力。借助这项能力,可以轻松地组织不同任务的运行顺序、

sermonlizhi的博客 12万+
上一篇: 什么? @ConditionalOnMissingBean 你没设置value?
下一篇: 封神之作,超火Java面试突击手册,进大厂真的就这么简单?
Java佳佳
博客等级 码龄4年 216粉丝 286原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值