【Java】CompletableFuture 异步任务编排
参考视频资料:
https://www.bilibili.com/video/BV1nA411g7d2
https://www.bilibili.com/video/BV1S54y1u79K
一、启动一个异步任务
runAsync 简单开启一个独立的线程,异步完成一个任务:
runAsync不会返回结果
public class AsyncThreadTest { @SneakyThrows
private static String getThreadName() {
return Thread.currentThread().getName();
} @SneakyThrows
private static void awaitSec() {
Thread.sleep(1000L);
} @SneakyThrows
private static void awaitSec(int sec) {
Thread.sleep(sec * 1000L);
}
/**
* 表示有两件事情需要同时执行
* 例如 开发一个功能 前端和后端同时进行
*/
@Test
@SneakyThrows
public void demo01() {
System.out.println("开会讨论需求"); // 前端开发前台页面
CompletableFuture<Void> async = CompletableFuture.runAsync(() -> {
int times = 6; // 假设前端开发耗时为6
while (times != 0) {
System.out.println(getThreadName() + " 前端开发前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
}); int times = 3; // 假设后端开发耗时为3
while (times != 0) {
System.out.println(getThreadName() +" 后端开发后台接口... 剩余时间 " + times);
awaitSec();
-- times;
} // join 等待该任务完成后才能继续下一步,如果不等待,主线程任务跑完了就会不等异步任务直接结束程序
async.join();
System.out.println("功能开发完成!");
} }
supplyAsync 和runAsync的区别就是能返回任务的结果:
阻塞主线程等待任务完成的方法有join和get,join和get都会返回任务的结果(阻塞主线程)
区别是join会封装异常处理,get要求手动处理异常
/**
* 如果需要知晓异步任务的结果,设置返回值返回
*/
@Test
@SneakyThrows
public void demo02() {
System.out.println("开会讨论需求"); // 前端开发前台页面
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
int times = 6; // 假设前端开发耗时为6
while (times != 0) {
System.out.println(getThreadName() + " 前端开发前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
return "编写的操作手册";
}); int times = 3; // 假设后端开发耗时为3
while (times != 0) {
System.out.println(getThreadName() + " 后端开发后台接口... 剩余时间 " + times);
Thread.sleep(1000L);
-- times;
} // join 等待该任务完成后才能继续下一步 (和get一样 get要求处理异常, join不要求)
String res = async.join();
// get方法本身自带join 阻塞执行
String res2 = async.get();
System.out.println("功能开发完成!" + res + " " + res2);
}
二、布置多个异步任务
任务链上的结果类型必须一致,不可以一会thenRun一会thenApply这样调用
thenCompose方法,在上一个任务完成后再执行入参的异步任务
要求入参一个CompletionStage的子类实例
/**
* thenCompose方法,当之前一个任务完成之后再开始执行方法内的异步任务
* 连接上一个任务
*/
@Test
@SneakyThrows
public void demo03() {
System.out.println("开会讨论需求"); // 前端开发前台页面
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
int times = 6; // 假设前端开发耗时为6
while (times != 0) {
System.out.println(getThreadName() + " 前端开发前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
return "页面组件编写完成!";
}).thenCompose(before -> CompletableFuture.supplyAsync(() -> {
int times = 3; // 假设前端开发耗时为
while (times != 0) {
System.out.println(getThreadName() + " 前端开发联调接口... 剩余时间 " + times);
awaitSec();
--times;
} return "接口联调完成!" + before;
})); int times = 3; // 假设后端开发耗时为3
while (times != 0) {
System.out.println(getThreadName() + " 后端开发后台接口... 剩余时间 " + times);
awaitSec();
-- times;
} String res = async.join();
// String res2 = async.get();
// System.out.println("功能开发完成!" + res + " " + res2);
System.out.println("功能开发完成!" + res);
}
thenApply方法只需要你提供任务逻辑即可
thenApply沿用上一个任务的线程,给当前任务使用
/**
* thenApply 方法,当之前一个任务完成之后再开始执行方法内的异步任务
* 连接上一个任务
* 和thenCompose不一样,不需要返回CompleteStage实现,默认放置了,只需要逻辑
*/
@Test
@SneakyThrows
public void demo06() {
System.out.println("开会讨论需求"); // 前端开发前台页面
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
int times = 6; // 假设前端开发耗时为6
while (times != 0) {
System.out.println(getThreadName() + " 前端开发前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
return "页面组件编写完成!";
}).thenApply(before -> {
int times = 3; // 假设前端开发耗时为
while (times != 0) {
System.out.println(getThreadName() + " 前端开发联调接口... 剩余时间 " + times);
awaitSec();
--times;
} return "接口联调完成!" + before;
}); int times = 3; // 假设后端开发耗时为3
while (times != 0) {
System.out.println(getThreadName() + " 后端开发后台接口... 剩余时间 " + times);
awaitSec();
-- times;
} String res = async.join();
// String res2 = async.get();
// System.out.println("功能开发完成!" + res + " " + res2);
System.out.println("功能开发完成!" + res);
}
thenApplyAsync方法在自定义线程池入参时,可能开一个新线程来执行
/**
* thenApplyAsync 方法, 使用自定义线程池入参时,会新开一个线程执行任务
*
*/
@Test
@SneakyThrows
public void demo07() {
System.out.println("开会讨论需求"); // 前端开发前台页面
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
int times = 6; // 假设前端开发耗时为6
while (times != 0) {
System.out.println(getThreadName() + " 前端开发前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
return "页面组件编写完成!";
}).thenApplyAsync(before -> {
int times = 3; // 假设前端开发耗时为
while (times != 0) {
System.out.println(getThreadName() + " 前端开发联调接口... 剩余时间 " + times);
awaitSec();
--times;
} return "接口联调完成!" + before;
}); int times = 3; // 假设后端开发耗时为3
while (times != 0) {
System.out.println(getThreadName() + " 后端开发后台接口... 剩余时间 " + times);
awaitSec();
-- times;
} String res = async.join();
// String res2 = async.get();
// System.out.println("功能开发完成!" + res + " " + res2);
System.out.println("功能开发完成!" + res);
}
thenRun 不知晓任务结果,不返回处理结果
/**
* thenRun 将不会返回结果,对任务完成之后执行一些你想做的事情
* (不会把返回结果传入)
*/
@Test
public void demo12() {
System.out.println("周一上班"); CompletableFuture<Void> empty = CompletableFuture.supplyAsync(() -> {
int times = 6; // 假设前端开发耗时为6
while (times != 0) {
System.out.println(getThreadName() + " 前端开发前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
return "页面组件编写完成!";
}).thenRun(() -> {
// 上一个任务完成后调用
System.out.println("今天的任务完成");
System.out.println("提前下班回家");
});
empty.join();
}
thenAccept 对上一个任务完成的通知
传入上一个任务结果,不会返回这个处理结果
/**
* thenAccept 将不会返回结果,对任务完成之后执行一些你想做的事情
* (会把返回结果传入)
*/
@Test
public void demo11() {
System.out.println("周一上班"); CompletableFuture<Void> empty = CompletableFuture.supplyAsync(() -> {
int times = 6; // 假设前端开发耗时为6
while (times != 0) {
System.out.println(getThreadName() + " 前端开发前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
return "页面组件编写完成!";
}).thenAccept(result -> {
// 上一个任务完成后调用
System.out.println("今天的任务完成:" + result);
System.out.println("提前下班回家");
});
empty.join();
}
thenCombine 方法整合两个异步任务,合并任务结果处理返回
/**
* thenCombine方法,追加一个新的异步任务,和之前的任务同时启动,两个任务都执行完成后回调一个合并方法,返回结果
* 合并两个任务 异步的线程使用同一个
*/
@Test
@SneakyThrows
public void demo05() {
System.out.println("开会讨论需求"); // 前端开发前台页面 现在需要两个前端来开发页面
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
int times = 3;
while (times != 0) {
System.out.println("前端开发1前台页面... 剩余时间 " + times);
awaitSec();
--times;
}
return "页面组件1编写完成!";
}).thenCombine(CompletableFuture.supplyAsync(() -> {
int times = 3;
while (times != 0) {
System.out.println("前端开发2前台页面... 剩余时间 " + times);
awaitSec();
--times;
} return "页面组件2编写完成";
}), (a, b) -> {
System.out.println(a);
System.out.println(b);
System.out.println("先摸会儿鱼再说");
return "页面完成";
}); int times = 3; // 假设后端开发耗时为3
while (times != 0) {
System.out.println("后端开发后台接口... 剩余时间 " + times);
awaitSec();
-- times;
} String res = async.join();
// String res2 = async.get();
// System.out.println("功能开发完成!" + res + " " + res2);
System.out.println("功能开发完成!" + res);
}
三、多任务结果处理
applyToEither方法,取最先完成任务的结果处理
/**
* applyToEither 同时执行两个异步任务,取最先完成的任务的结果返回,附带一个处理方法
*
*/
@Test
@SneakyThrows
public void demo08() {
System.out.println("开会讨论需求");
// 周一上班 两种情况
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
awaitSec();
awaitSec();
System.out.println("老板没来,摸鱼!");
return "今天我tm摸爆";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
awaitSec();
System.out.println("老板来了,干活!");
return "今天我被老板爆叼";
}), toDay -> "今天的结果是:" + toDay); String res = async.join();
System.out.println(res);
}
acceptEither,消费最先完成任务的结果(不返回结果)
/**
* acceptEither 同时执行两个异步任务,取最先完成的任务的结果返回,附带一个处理方法
* (applyToEither 会返回处理结果,acceptEither不处理结果返回,纯消费)
*
*/
@Test
@SneakyThrows
public void demo17() {
System.out.println("开会讨论需求");
// 周一上班 两种情况
CompletableFuture<Void> async = CompletableFuture.supplyAsync(() -> {
awaitSec();
awaitSec();
System.out.println("老板没来,摸鱼!");
return "今天我tm摸爆";
}).acceptEither(CompletableFuture.supplyAsync(() -> {
awaitSec();
System.out.println("老板来了,干活!");
return "今天我被老板爆叼";
}), System.out::println); async.join();
}
runAfterEither 通知有个任务最先完成了
/**
* runAfterEither 同时执行两个异步任务,不关心哪个任务最快,只是最快的任务执行完后通知你要做点什么
* 没有返回值也没有入参
*/
@Test
@SneakyThrows
public void demo18() {
System.out.println("开会讨论需求");
// 周一上班 两种情况
CompletableFuture<Void> async = CompletableFuture.supplyAsync(() -> {
awaitSec();
awaitSec();
System.out.println("老板没来,摸鱼!");
return "今天我tm摸爆";
}).runAfterEither(CompletableFuture.supplyAsync(() -> {
awaitSec();
System.out.println("老板来了,干活!");
return "今天我被老板爆叼";
}), () -> System.out.println("ssss") ); async.join();
}
allOf方法,异步组任务完成后通知需要做什么
/**
* allOf 所有置入的异步任务全部完成后调用此任务
* (allOf的任务不接受所有的任务的结果),必须要提取任务引用,一个个手动join获取
* 或者封装到集合容器遍历join
*/
@Test
public void demo15() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
awaitSec(3);
return "Future1的结果";
}); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
awaitSec(4);
return "Future2的结果";
}); CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
awaitSec(5);
return "Future3的结果";
}); CompletableFuture<String>[] futures = new CompletableFuture[]{future1, future2, future3}; // step 4: 使用allOf方法合并多个异步任务
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(futures); // step 5: 当多个异步任务都完成后,使用回调操作文件结果,统计符合条件的文件个数
CompletableFuture<String> countFuture = allOfFuture.thenApply(v -> Arrays.stream(futures).map(f -> f.join()).collect(Collectors.joining())); // step 6: 主线程打印输出文件个数
System.out.println("count = " + countFuture.join());
}
anyOf 方法, 任务组中最先完成的任务后通知需要做什么
/**
* anyOf 所有置入的异步任务中,获取最快完成任务的结果
*
*/
@Test
public void demo14() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
awaitSec(3);
return "Future1的结果";
}); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
awaitSec(4);
return "Future2的结果";
}); CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
awaitSec(5);
return "Future3的结果";
}); CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2, future3); // 输出Future2的结果
System.out.println(anyOfFuture.join());
}
四、任务异常捕获
exceptionally方法捕获任务链上的异常
(不捕获任务链异常,任务直接中断执行,无法查看在哪一层任务的异常)
/**
* exceptionally 在上面的任务中出现异常进入该方法,并将这个异常传入
* (异步任务链上的所有异常都会被 exceptionally 捕获) 一般放在在最后声明异常的处理
*
*/
@Test
@SneakyThrows
public void demo09() {
System.out.println("开会讨论需求");
// 周一上班 两种情况
CompletableFuture<String> async = CompletableFuture.supplyAsync(() -> {
awaitSec();
awaitSec();
System.out.println("老板没来,摸鱼!");
return "今天我tm摸爆";
}).exceptionally(e -> {
// 这里记录错误日志
return "老板高兴直接宣布今天休息!直接下班";
}).applyToEither(CompletableFuture.supplyAsync(() -> {
awaitSec();
System.out.println("老板来了,干活!");
if (true) throw new RuntimeException();
return "今天我被老板爆叼";
}), toDay -> "今天的结果是:" + toDay).exceptionally(e -> {
// 这里记录错误日志
return "老板高兴直接宣布今天休息!直接下班";
}); String res = async.join();
System.out.println(res);
}
handle方法 捕获上个任务中可能出现的异常,保证这个任务的执行不会中断
/**
* handle方法,在任务链中处理前一次的异常,不会报错,继续传递结果给下一个任务
*/
@Test
public void demo16() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
int r = 1 / 0;
return "result1";
}).handle((ret, ex) -> {
if (ex != null) {
System.out.println("我们得到异常:" + ex.getMessage());
return "Unknown1";
}
return ret;
}).thenApply(result -> {
String str = null;
int len = str.length();
return result + " result2";
}).handle((ret, ex) -> {
if (ex != null) {
System.out.println("我们得到异常:" + ex.getMessage());
return "Unknown2";
}
return ret;
}).thenApply(result -> {
return result + " result3";
}); String ret = future.join();
}
【Java】CompletableFuture 异步任务编排的更多相关文章
- Java CompletableFuture 异步超时实现探索
作者:京东科技 张天赐 前言 JDK 8 是一次重大的版本升级,新增了非常多的特性,其中之一便是 CompletableFuture.自此从 JDK 层面真正意义上的支持了基于事件的异步编程范式,弥补 ...
- 使用CompletableFuture进行异步任务编排
1.JDK5引入了Future进行异步任务的处理,Future 的接口主要方法有以下几个: (1)boolean cancel (boolean mayInterruptIfRunning) 取消任务 ...
- CompletableFuture异步编排
什么是CompletableFuture CompletableFuture是JDK8提供的Future增强类.CompletableFuture异步任务执行线程池,默认是把异步任务都放在ForkJo ...
- Java CompletableFuture 详解
Future是Java 5添加的类,用来描述一个异步计算的结果.你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执 ...
- 使用 CompletableFuture 异步组装数据
使用 CompletableFuture 异步组装数据 一种快捷.优雅的异步组装数据方式 实际项目中经常遇到这种情况: 从多个表中查找到数据然后拼装成一个VO返回给前端. 这个过程有可能会非常耗时.因 ...
- java8中CompletableFuture异步处理超时
java8中CompletableFuture异步处理超时的方法 Java 8 的 CompletableFuture 并没有 timeout 机制,虽然可以在 get 的时候指定 timeout,但 ...
- [html5+java]文件异步读取及上传核心代码
html5+java 文件异步读取及上传关键代码段 功能: 1.多文件文件拖拽上传,file input 多文件选择 2.html5 File Api 异步FormData,blob上传,图片显示 3 ...
- java后台异步任务执行器TaskManager
java后台异步任务执行器TaskManager 此方式基于MVC方式: 一,使用任务: @Resource private TaskManager taskManager; public strin ...
- Java TCP异步数据接收
之前一直采用.Net编写服务端程序,最近需要切换到Linux平台下,于是尝试采用Java编写数据服务器.TCP异步连接在C#中很容易实现,网上也有很多可供参考的代码.但Java异步TCP的参考资料较少 ...
- 利用回调实现Java的异步调用
异步是指调用发出后,调用者不会立刻得到结果,而是在调用发出后,被调用者通知调用者,或通过回调函数处理这个调用. 回调简单地说就是B中有一个A,这样A在调用B的某个方法时实际上是调用到了自己的方法. 利 ...
随机推荐
- Wgpu图文详解(01)窗口与基本渲染
写在前面 如果对Rust与Wgpu比较关注的同学可能在网络上搜到过@sotrh国外大佬编写的<Learn Wgpu>,以及国内大佬@jinleili的优秀翻译作品<学习 Wgpu&g ...
- Java中try catch finally 关键字
异常处理中的几个常用关键字(try catch finally throw throws) 异常处理 java中提供一套异常处理机制,在程序发生异常时,可以执行预先设定好的处理程序, 执行完成后,程序 ...
- Android应用程序启动流程浅析-(三万字长文慎点&Android14)
在Android桌面Launcher源码浅析中介绍了Android的桌面程序Launcher是如何响应用户点击事件并启动App的,这篇文章继续介绍App在Android系统层是的启动流程. 一.启动流 ...
- ETL工具-nifi干货系列 第一讲 揭开nifi神秘面纱
1.nifi简介 Apache NiFi 是基于流程编程概念的数据流系统.它支持强大且可扩展的数据路由.转换和系统中介逻辑的有向图.NiFi具有基于Web的用户界面,用于设计.控制.反馈和监控数据流. ...
- shell中各个括号的用法区别
在 shell 脚本中,[ ].[[ ]].( ).(( )).{ } 和 {{ }} 都有各自特定的用法和区别.下面是对这些结构的详细解释: 1. [ ] (test 命令) [ ] 是 shell ...
- CSS和CSS3(背景,图片,浮动等)
CSS和CSS3背景图片 CSS的背景,无法伸缩图片. <!DOCTYPE html> <html lang="en"> <head> < ...
- Linux内核中的各种文件系统:proc、tmpfs、devfs、sysfs
Linux内核中的各种文件系统:proc.tmpfs.devfs.sysfs 背景 刚学完proc文件系统在内核驱动 中的使用,就看到另外的sysfs的有关接口.很好奇proc文件系统和sysfs文件 ...
- ACPI Table 与 Device Tree
背景 在分析Linux内核驱动的时候,有时候会看到一些acpi字样的接口. 之前一直没搞明白ACPI是什么,现在知道了. Reference : https://www.cnblogs.com/jun ...
- QT学习:08 QString
--- title: framework-cpp-qt-08-QString EntryName: framework-cpp-qt-08-QString date: 2020-04-16 15:36 ...
- 对Transformer的一些理解
在学习Transformer这个模型前对seq2seq架构有个了解时很有必要的 先上图 输入和输出 首先理解模型时第一眼应该理解输入和输出最开始我就非常纠结 有一个Inputs,一个Outputs(s ...