Java8新特性CompletableFuture详解

一、概述
CompletableFuture 是Java 8 中引入的 Java Future API的扩展,用于 Java 中的异步编程,它可以使我们的任务运行在与主线程分离的其他线程中,并通过回调在主线程中得到异步任务执行状态,包括是否完成,是否异常等信息。
这样,主线程不会阻塞/等待任务的完成,它可以并行执行其他任务。拥有这种并行性极大地提高了程序的性能。

二、为什么要引入CompletableFuture?
在一些业务场景中我们需要使用多线程异步执行任务,所以Java 1.5 推出的Callable和Future接口解决这个问题,但是因为Future有几个局限:
1.Future的get方法会导致主线程阻塞
2.轮询获取结果会消耗cpu资源。
3.多个Future任务不能按照顺序执行。
4.Future Api无异常处理。
举个例子:比如公司需要发货某样商品,发现库存不足,通知采购去采购一些商品的需求。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;/*** 发货Future实现*/
public class SendGoods {/*** 主线程为发货商品** @param args*/public static void main(String[] args) {System.out.println("发货start");ExecutorService executorService = Executors.newFixedThreadPool(1);FutureTask<String> futureTask = new FutureTask<>(() -> {Thread.sleep(1000);return "采购的商品";});//发现库存不够,提交异步任务(采购货物)executorService.submit(futureTask);/*** 方法1* 局限:导致线程堵塞*/try {//获取采购的商品(堵塞线程)String goods = futureTask.get();System.out.println("采购的商品:" + goods);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}/*** 方法2* 通过while轮询方式会消耗cpu*/while (true) {if (futureTask.isDone()) {try {//获取采购的商品(堵塞线程)String goods = futureTask.get();System.out.println("采购的商品:" + goods);break;} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}} else {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}System.out.println("发货end");}
}   

实例代码提供了两种方式,但都是有局限性并且堵塞了主线程。
因此Java8引入了CompletableFuture来解决这些问题。

三、创建 CompletableFuture
1.CompletableFuture提供了以下静态方法:

// 无返回值
public static CompletableFuture<Void> runAsync(Runnable runnable)
// 无返回值 可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
// 有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
// 有返回值 可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

supply开头:这种方法,可以返回异步线程执行之后的结果。
run开头:这种不会返回结果,就只是执行线程任务。
例如:

CompletableFuture.runAsync(() -> System.out.println("执行无返回值的异步任务"));
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {System.out.println("执行有返回值的异步任务");return "finish";
});

接着我们可以通过get()或者join()方法来获取返回的结果。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {System.out.println("执行有返回值的异步任务");return "finish";
});
System.out.println(future.get());

2.thenApply()方法
我们可以使用thenApply()方法在 CompletableFuture 到达时对其进行处理和转换,它将Function < T,R >作为参数。Function < T,R >是一个简单的函数式接口,表示一个接受 T 类型参数并产生 R 类型结果的函数。
例如:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {// 模拟业务时长   TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {          throw new IllegalStateException(e);     }return "华为手机";
});CompletableFuture<String> resFuture = future.thenApply(name -> "订单: " + name);System.out.println(resFuture.get());

结果为:订单: 华为手机
你也可以通过附加一系列的thenApply()在回调方法 在CompletableFuture写一个连续的转换。这样的话,结果中的一个 thenApply方法就会传递给该系列的另外一个 thenApply方法。
例如:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {// 模拟业务时长   TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {          throw new IllegalStateException(e);     }return "华为手机";
});CompletableFuture<String> resFuture = future.thenApply(name -> "订单: " + name).thenApply(result-> result + ",。。。。。。。");System.out.println(resFuture.get());
  1. thenAccept() 和 thenRun()方法。
    如果不想从回调函数中返回任何东西,只想在 Future 完成后运行一些代码,那么你就可以使用thenAccept()和thenRun()方法。
    例如:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "华为手机");
future.thenAccept(product  -> System.out.println("我拿到了 " + product));

结果为:我拿到了 华为手机

注意:thenRun()无法拿到上一次的结果,thenRun()意为然后执行什么,里面接收一个Runnable参数。

在runAsync方法和supplyAsync方法第二个参数需要指定 线程池Executor,如果不指定,会使用默认的线程池ForkJoinPool。
ForkJoinPool可查大致为:

ForkJoinPool就是用来解决这种问题的:将一个大任务拆分成多个小任务后,
使用fork可以将小任务分发给其他线程同时处理,使用join可以将多个线程处理的结果进行汇总;这实际上就是分治思想的并行版本。

4、组合两个CompletableFuture
我们 使用 thenCompose()组合两个独立的future,假设我们想从一个远程API中获取一个用户的信息,当用户信息可用时,我们想从另外一个服务中获取另外的信息。那么我们就可以使用thenCompose()组合了。
如下:

CompletableFuture<User> getUser(String userId) {return CompletableFuture.supplyAsync(() -> {userService.getUser(userId);});	
}CompletableFuture<Double> getOtherInfo(User user) {return CompletableFuture.supplyAsync(() -> {otherService.getOtherInfo(user);});
}

首先先看下thenApply()方法。

CompletableFuture<CompletableFuture<Double>> result = getUser(userId)
.thenApply(user -> getOtherInfo(user));

这种方式只是返回一个嵌套的CompletableFuture,如果想获取最终的结果给最顶层future,使用 thenCompose()方法代替。

CompletableFuture<CompletableFuture<Double>> result = getUser(userId)
.thenCompose(user -> getOtherInfo(user));

因此,你想从CompletableFuture链中获取一个直接合并后的结果,这时候你可以使用thenCompose()。

另外,thenCombine()组合两个独立的 future 虽然thenCompose()被用于当一个future依赖另外一个future的时候用来组合两个future。thenCombine()被用来当两个独立的Future都完成的时候,用来做一些事情。

CompletableFuture<User> getUser(String userId) {return CompletableFuture.supplyAsync(() -> {userService.getUser(userId);});	
}CompletableFuture<User> getOther(String userId) {return CompletableFuture.supplyAsync(() -> {otherService.getOther(user);});
}
CompletableFuture<CompletableFuture<Double>> resultFuture = getUser(userId)
.thenCombine(getOther,(a, b)->{List users = new ArrayList<>();users.add(a);users.add(b);
});System.out.println("users is - " + resultFuture .get());

当两个Future都完成的时候,传给thenCombine()的回调函数将被调用。

5、组合多个CompletableFuture
如果想组合任意数量的CompletableFuture,可以使用CompletableFuture.allOf()。
CompletableFuture.allOf()里面包含了多个CompletableFuture操作。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {return andTree(cfs, 0, cfs.length - 1);
}

接收一个CompletableFuture类型的可变参数。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "用户信息");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "角色信息");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "部门信息");CompletableFuture.allOf(future1, future2, future3);
System.out.println(future1.join());
System.out.println(future2.join());
System.out.println(future3.join());

6.CompletableFuture.anyOf()
这种方式就是多个任务中哪个任务先返回我就返回结果。
例如:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {// 模拟业务时长ThreadUtil.sleep(3000);return "用户信息";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {// 模拟业务时长ThreadUtil.sleep(2000);return "角色信息";
});
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {return "部门信息";
});CompletableFuture<Object> future = CompletableFuture.anyOf(future1, future2, future3);
System.out.println(future.join());

结果:部门信息

7.CompletableFuture 异常处理
如果在执行过程中害怕出错,那么我们可以加上异常处理。
例如:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {int age = -1;if(age < 0) {throw new IllegalArgumentException("年龄不能为负数");}if(age > 18) {return "成年人";} else {return "小孩";}
}).exceptionally(e -> {System.out.println("年龄错误 " + e.getMessage());return "error!";
});
System.out.println(future.join());

此外handle()方法也可以处理异常。

8.complete() 方法。
CompletableFuture 类中的 complete() 方法用于手动完成一个异步任务,并设置其结果。通过调用 complete() 方法,可以将一个特定的结果设置到 CompletableFuture 对象中,然后任何等待该异步任务的操作都会得到这个预先设置的结果。

public class Result {public static void main(String[] args) {CompletableFuture future = CompletableFuture.supplyAsync(() ->{try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("进入异步方法");return 1;});try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(future.complete(2) + "," + future.join());}
}

大家可以自己去了解下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/203718.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Node.js】如何修复“错误:错误:0308010c:digital envelope routines::不受支持”

作为一名开发人员&#xff0c;在项目中遇到错误是很常见的。在这篇博客文章中&#xff0c;我们将讨论一个困扰开发人员的特定错误&#xff0c;尤其是那些使用Node.js的开发人员。有问题的错误是“错误&#xff1a;错误&#xff1a;0308010c:数字信封例程&#xff1a;&#xff1…

MySQL:update set的坑

目录 一、问题描述 二、为何会出现这样的问题&#xff1f; 三、正确的方案 一、问题描述 我在修改mysql数据表时&#xff0c;看到下面的现象。 我表中原始数据如下&#xff1a; 执行了下面的修改&#xff0c;显示执行成功。 update user_function_record_entity set open_…

MySQL_1. mysql数据库介绍

shell脚本差不多快完结了接下来会为大家更新MySQL系列的相关的基础知识笔记&#xff0c;希望对大家有所帮助&#xff0c;好废话不多说&#xff0c;接下来开始正题&#xff01; 1.mysql数据库介绍 mysql 是一款安全、跨平台、高效的&#xff0c;并与 PHP、Java 等主流编程语言…

AI写作工具有哪些?原创我AI写作工具推荐

人工智能&#xff08;AI&#xff09;的广泛应用不仅改变了我们的工作方式&#xff0c;也对文学创作领域产生了深远的影响。其中&#xff0c;AI写作技术在提高工作效率和文章创作方面发挥着越来越重要的角色。然而&#xff0c;伴随着这一技术的兴起&#xff0c;一个备受关注的问…

机器学习实验五:集成学习

系列文章目录 机器学习实验一&#xff1a;线性回归机器学习实验二&#xff1a;决策树模型机器学习实验三&#xff1a;支持向量机模型机器学习实验四&#xff1a;贝叶斯分类器机器学习实验五&#xff1a;集成学习机器学习实验六&#xff1a;聚类 文章目录 系列文章目录一、实验…

Eureka的使用说明

Eureka是一个服务管理的平台&#xff0c;主要是管理多个模块之间的使用。eureka分为客户端和客户端&#xff0c;下面我们直接使用&#xff1a; 1.eureka server 服务管理的使用 1. 导入相关依赖 <dependency><groupId>org.springframework.cloud</groupId>…

docker基本管理和相关概念

1、docker是什么&#xff1f; docker是开源的应用容器引擎。基于go语言开发的&#xff0c;运行在Linux系统当中开源轻量级的“虚拟机”。 docker可以在一台主机上轻松的为任何应用创建一个轻量级的&#xff0c;可移植的&#xff0c;自给自足的容器。docker的宿主机是Linux系统…

【PID学习笔记 7 】控制系统的性能指标之三

写在前面 控制系统性能指标有单项指标和综合指标两类&#xff0c;上文重点介绍了单项指标&#xff0c;本文将介绍系统阶跃响应的综合性能指标。 一、系统阶跃响应的综合性能指标 单项指标虽然清晰明了&#xff0c;但如何统筹考虑比较困难。而偏差幅度和偏差存在的时间都与偏…

看图学源码 之 Atomic 类源码浅析一(cas + 自旋操作的 AtomicXXX原子类)

Atomic 针对基本数据类型的单个变量 AtomicInteger // 就是对 value 执行操作 public class AtomicInteger extends Number implements java.io.Serializable {...// 拿到 Unsafe 类 private static final Unsafe unsafe Unsafe.getUnsafe();// 偏移量&#xff1a; 字段…

1-Tornado的介绍

1 tornado的介绍 **Tornado**是一个用Python编写的可扩展的、无阻塞的**Web应用程序框架**和**Web服务器**。 它是由FriendFeed开发使用的&#xff1b;该公司于2009年被Facebook收购&#xff0c;而Tornado很快就开源了龙卷风以其高性能着称。它的设计允许处理大量并发连接&…

Spark常见算子汇总

创建RDD 在Spark中创建RDD的方式分为三种: 从外部存储创建RDD从集合中创建RDD从其他RDD创建 textfile 调用SparkContext.textFile()方法&#xff0c;从外部存储中读取数据来创建 RDD parallelize 调用SparkContext 的 parallelize()方法&#xff0c;将一个存在的集合&…

这些Java并发容器,你都了解吗?

文章目录 前言并发容器1.ConcurrentHashMap 并发版 HashMap示例 2.CopyOnWriteArrayList 并发版 ArrayList示例 3.CopyOnWriteArraySet 并发 Set示例 4.ConcurrentLinkedQueue 并发队列 (基于链表)示例 5.ConcurrentLinkedDeque 并发队列 (基于双向链表)示例 6.ConcurrentSkipL…

Vue学习计划-Vue2--Vue核心(五)条件、列表渲染、表单数据

1. 条件渲染 v-if v-if“表达式”v-else-if “表达式”v-else “表达式” 适用于&#xff1a;切换频率较低的场景 特点&#xff1a;不显示dom元素&#xff0c;直接被删除 注意&#xff1a;v-if和v-else-if、v-else一起使用&#xff0c;但要求结构不能被打断 v-if和template一…

Android笔记(十七):PendingIntent简介

PendingIntent翻译成中文为“待定意图”&#xff0c;这个翻译很好地表示了它的涵义。PendingIntent描述了封装Intent意图以及该意图要执行的目标操作。PendingIntent封装Intent的目标行为的执行是必须满足一定条件&#xff0c;只有条件满足&#xff0c;才会触发意图的目标操作。…

Kotlin 中的 also 和 run:选择正确的作用域函数

在 Kotlin 中&#xff0c;also 和 run 是两个十分有用的作用域函数。 虽然它们在功能上相似&#xff0c;但各自有独特的用途和适用场景。 一、分析&#xff1a; also&#xff1a;在对象的上下文中执行给定的代码块&#xff0c;并返回对象本身。它的参数是一个接收对象并返回…

分布式分布式事务分布式锁分布式ID

目录 分布式分布式系统设计理念目标设计思路中心化去中心化 基本概念分布式与集群NginxRPC消息中间件&#xff08;MQ&#xff09;NoSQL&#xff08;非关系型数据库&#xff09; 分布式事务1 事务2 本地事务3 分布式事务4 本地事务VS分布式事务5 分布式事务场景6 CAP原理7 CAP组…

ChatGPT发展历程

ChatGPT是一个在2020年成立的在线聊天平台&#xff0c;它的发展历程如下&#xff1a; 初期阶段&#xff1a;2020年&#xff0c;在全球疫情爆发的情况下&#xff0c;ChatGPT创始人开始思考如何为人们提供一个快捷、安全、便利的在线聊天平台。他们选择使用GPT&#xff08;生成对…

(2/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)

附录 A1 - 《PMBOK指南》映射 表A1显示了第六版《PMBOK指南》中定义的项目管理过程组与知识领域之间的对应关系 本附录说明了如何利用混合和敏捷方法处理《PMBOK指南》知识领域&#xff08;请参见表A1-2&#xff09;中所述的属性&#xff0c;其中涵盖了相同和不同的属性&…

conda 安装教程分享

大家好&#xff0c;我是微赚淘客系统的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我将向大家介绍如何使用conda进行安装。 首先&#xff0c;让我们来了解一下conda。conda是Anaconda发行版的一部分&#xff0c;它是一个开源的包管理系…