优雅处理并发:Java CompletableFuture最佳实践

7T7AlJ.png

第1章:引言

大家好,我是小黑,今天,小黑要和大家聊聊CompletableFuture,这个Java 8引入的强大工具。

在Java传统的Future模式里,咱们都知道,一旦开始了一个异步操作,就只能等它结束,无法知道执行情况,也不能手动完成或者取消。而CompletableFuture呢,就像它的名字一样,是可以"完全控制"的Future。它提供了更多的控制,比如可以手动完成,可以处理异常,还可以把多个Future组合起来,进行更复杂的异步逻辑处理。

对于现代Java程序员来说,掌握CompletableFuture是必不可少的。无论是提高程序的响应性能,还是编写更加清晰、更具可读性的代码,它都能大显身手。

第2章:基本概念解读

那么,CompletableFuture到底是什么呢?简单来说,它是一种异步编程工具,可以帮助咱们在未来的某个时刻完成一个计算结果。与Future最大的不同是,它可以被显式地完成,意味着咱们可以在任何时候设置它的值。

让我们来看一个简单的例子。假设小黑要从网上查询某个产品的价格,这是一个耗时的操作,使用CompletableFuture,咱们就可以异步地完成这个任务:

import java.util.concurrent.CompletableFuture;public class CompletableFutureDemo {public static void main(String[] args) {// 创建一个CompletableFuture实例CompletableFuture<String> futurePrice = CompletableFuture.supplyAsync(() -> {// 模拟耗时操作,比如调用外部APIsimulateDelay();return "100元";});// 在这里,咱们可以做一些其他的事情,不必等待价格查询的结果doSomethingElse();// 当结果准备好后,获取它String price = futurePrice.join();System.out.println("价格是:" + price);}private static void simulateDelay() {try {Thread.sleep(1000); // 模拟1秒的延迟} catch (InterruptedException e) {Thread.currentThread().interrupt();}}private static void doSomethingElse() {// 做一些其他的事情System.out.println("小黑在做其他的事情...");}
}

在这个例子中,supplyAsync方法创建了一个异步操作,模拟了一个耗时的价格查询过程。在查询价格的同时,主线程可以继续执行其他任务,比如doSomethingElse方法里的内容。当价格查询完成后,可以使用join方法来获取结果。这样的处理方式,让整个程序的执行效率大大提升,而且代码也更简洁明了。

CompletableFuture的美在于,它提供了一种新的编程范式,让咱们能够以声明式的方式描述复杂的异步逻辑。从上面的例子可以看出,CompletableFuture不仅让代码更加简洁,还让逻辑更加清晰,易于理解和维护。

第3章:创建CompletableFuture

1. 使用supplyAsync

最常见的创建方式是使用CompletableFuture.supplyAsync()。这个方法需要一个Supplier函数接口,通常用于执行异步计算。来看看小黑怎么用:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 模拟耗时的计算simulateTask("数据加载中");return "结果";
});

这个例子中,simulateTask模拟了一个耗时操作,比如从数据库加载数据。使用supplyAsync,咱们就能在另一个线程中执行这个任务,而主线程可以继续做其他事情。

2. 使用runAsync

如果咱们不关心异步任务的结果,只想执行一个异步操作,那就可以用runAsync。它接受一个Runnable函数接口,不返回任何结果:

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {simulateTask("正在执行一些处理");
});

在这个例子里,simulateTask只是执行了一些操作,比如记录日志或者发送通知,但不返回任何内容。

3. 手动完成

有时候,咱们可能需要手动完成一个Future。比如,基于某些条件判断,决定是否提前返回结果。这时候可以用complete方法:

CompletableFuture<String> manualFuture = new CompletableFuture<>();
// 在某些条件下手动完成Future
if (checkCondition()) {manualFuture.complete("手动结果");
}

如果checkCondition返回true,那么这个Future就会被立即完成,否则它将保持未完成状态。

4. 组合使用

CompletableFuture真正的魅力在于它的组合能力。假设小黑有两个独立的异步任务,咱们可以这样组合它们:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {simulateTask("加载用户数据");return "用户小黑";
});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {simulateTask("加载配置信息");return "配置信息";
});// 组合两个future,等待它们都完成
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (user, config) -> {return "处理结果: " + user + "," + config;
});

在这个例子中,thenCombine用于组合future1future2的结果。只有当这两个Future都完成时,才会调用thenCombine里的函数。

第4章:异步操作和链式调用

异步操作的力量

异步操作是指在一个线程中启动一个任务,让它在另一个线程中运行,从而不阻塞当前线程的执行。这在处理耗时任务时特别有用。举个例子,假设咱们要查询数据库,然后处理查询结果。如果同步执行,整个程序都得等着数据库查询完成,这就浪费了宝贵的时间。但如果用CompletableFuture实现异步,就可以在查询数据库的同时做其他事情。

链式调用的魅力

链式调用则是指一系列操作依次执行,前一个操作的结果作为下一个操作的输入。CompletableFuture支持多种链式调用方法,比如thenApply, thenAcceptthenRun

  • thenApply用于处理和转换CompletableFuture的结果。
  • thenAccept用于消费CompletableFuture的结果,不返回新的CompletableFuture。
  • thenRun则不关心前一个任务的结果,只是在前一个任务执行完后,执行一些后续操作。

来看看小黑准备的例子:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {simulateTask("查询数据库");return "查询结果";
});future.thenApply(result -> {// 对结果进行处理return "处理后的结果:" + result;
}).thenAccept(processedResult -> {// 消费处理后的结果System.out.println("最终结果:" + processedResult);
}).thenRun(() -> {// 执行一些不需要前一个结果的操作System.out.println("所有操作完成");
});

在这个例子里,小黑用supplyAsync启动了一个异步任务来查询数据库。然后用thenApply处理查询结果,用thenAccept消费处理后的结果,最后用thenRun标记所有操作完成。

通过这种方式,咱们可以构建出复杂的异步逻辑,而代码却依然保持清晰和易于管理。这就是CompletableFuture的魅力所在。

第5章:异常处理

基本异常处理

在CompletableFuture的世界里,如果异步操作失败了,异常会被捕获并存储在Future对象中。咱们可以使用exceptionally方法来处理这些异常。这个方法会返回一个新的CompletableFuture,它会在原来的Future抛出异常时执行。

来看个例子:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {if (new Random().nextBoolean()) {throw new RuntimeException("出错啦!");}return "正常结果";
}).exceptionally(ex -> {return "错误的回退结果:" + ex.getMessage();
});future.thenAccept(System.out::println);

这里,小黑创建了一个可能会失败的异步操作。如果抛出异常,exceptionally方法就会被调用,返回一个包含错误信息的回退结果。

细粒度的异常处理

有时候,咱们可能需要更细粒度的控制,比如只处理特定类型的异常,或者在异常发生时还想继续其他操作。这时候,可以用handle方法。它可以同时处理正常的结果和异常情况。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {if (new Random().nextBoolean()) {throw new RuntimeException("出错啦!");}return "正常结果";
}).handle((result, ex) -> {if (ex != null) {return "处理异常:" + ex.getMessage();}return "处理结果:" + result;
});future.thenAccept(System.out::println);

在这个例子中,无论异步操作是成功还是失败,handle方法都会被调用。如果有异常,它会处理异常;如果没有,就处理正常结果。

管道式异常处理

CompletableFuture还允许咱们创建一个异常处理的“管道”,这样就可以把多个异步操作链接起来,并在链的任意位置处理异常。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 第一个异步操作return "第一步结果";
}).thenApply(result -> {// 第二个异步操作,可能会出错throw new RuntimeException("第二步出错啦!");
}).exceptionally(ex -> {// 处理异常return "在第二步捕获异常:" + ex.getMessage();
}).thenApply(result -> {// 第三个异步操作return "第三步使用结果:" + result;
});future.thenAccept(System.out::println);

在这个例子中,小黑创建了一个包含三个步骤的异步操作链。如果第二步出错,异常会被捕获并处理,然后处理结果被传递到第三步。

第6章:组合与依赖

组合多个Future

最常用的方法之一是thenCombine。这个方法允许你组合两个独立的CompletableFuture,并且当它们都完成时,可以对它们的结果进行一些操作。

来看个例子:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {simulateTask("加载用户信息");return "用户小黑";
});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {simulateTask("加载订单数据");return "订单123";
});CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (userInfo, orderInfo) -> {return "合并结果:" + userInfo + "," + orderInfo;
});combinedFuture.thenAccept(System.out::println);

在这个例子中,future1future2代表两个独立的异步操作。只有当两者都完成时,thenCombine里面的函数才会执行,并且合并它们的结果。

依赖关系的处理

如果你的一个异步操作依赖于另一个异步操作的结果,那么可以使用thenCompose方法。这个方法允许你在一个Future完成后,以其结果为基础启动另一个异步操作。

CompletableFuture<String> masterFuture = CompletableFuture.supplyAsync(() -> {simulateTask("获取主数据");return "主数据结果";
});CompletableFuture<String> dependentFuture = masterFuture.thenCompose(result -> {return CompletableFuture.supplyAsync(() -> {simulateTask("处理依赖于" + result + "的数据");return "处理后的数据";});
});dependentFuture.thenAccept(System.out::println);

这个例子中,dependentFuture的执行依赖于masterFuture的结果。

处理多个Future

有时候,咱们可能有多个异步操作,需要等所有操作都完成后再进行下一步。这时候,可以使用CompletableFuture.allOf

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {simulateTask("任务一");return "结果一";
});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {simulateTask("任务二");return "结果二";
});CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);allFutures.thenRun(() -> {System.out.println("所有任务完成");
});

allOf会等待所有提供的Futures完成,然后执行后续操作。

第7章:最佳实践

1. 明智地选择异步任务执行方式

CompletableFuture提供了多种执行异步任务的方法,比如runAsyncsupplyAsync。默认情况下,它们使用公共的ForkJoinPool,但在某些场景下,你可能想要使用自定义的线程池来更好地控制资源。

ExecutorService customExecutor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {return "使用自定义线程池";
}, customExecutor);

这样做可以让你更好地管理线程资源,尤其是在处理大量异步任务时。

2. 谨慎处理阻塞操作

如果你的CompletableFuture链中包含阻塞调用,如数据库操作或文件I/O,最好是将这些操作放在独立的线程池中,避免阻塞ForkJoinPool中的线程。

ExecutorService dbExecutor = Executors.newCachedThreadPool();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {// 这里是阻塞的数据库操作simulateTask("数据库操作");
}, dbExecutor);

这样可以防止长时间的阻塞操作占用过多的计算资源,影响整体性能。

3. 组合异步操作时的错误处理

当你组合多个CompletableFuture时,记得对每一个Future都进行错误处理。这样可以避免一个未捕获的异常破坏整个操作链。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "任务1").exceptionally(ex -> "默认值1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "任务2").exceptionally(ex -> "默认值2");CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + " 和 " + result2);

这样做确保了即使其中一个操作失败,整个链也可以继续执行。

4. 避免过多的链式调用

虽然链式调用是CompletableFuture的一个强大特性,但过度使用可能会导致代码难以理解和维护。建议把复杂的逻辑分解成多个方法或类。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "原始数据").thenApply(this::step1).thenApply(this::step2).thenApply(this::step3);// 将每个步骤的逻辑封装在不同的方法中
private String step1(String data) {return "处理1:" + data;
}private String step2(String data) {return "处理2:" + data;
}private String step3(String data) {return "处理3:" + data;
}

第8章:总结

  1. 异步编程的强大工具:CompletableFuture为Java异步编程提供了强大的支持,让处理并发任务变得更简单、更灵活。

  2. 简化复杂逻辑:通过链式调用和组合多个异步任务,CompletableFuture能够帮助咱们以清晰的方式处理复杂的业务逻辑。

  3. 异常处理的优雅方式:CompletableFuture提供了一套完整的异常处理框架,让咱们能够更好地控制和管理异步代码中的错误情况。

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

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

相关文章

x-cmd pkg | magick - 开源图像处理工具

目录 简介首次用户功能特点类似工具与竞品进一步探索 简介 magick 是由 ImageMagick 提供的一个功能强大且多功能的开源图像处理工具&#xff0c;可以灵活高效地处理图像文件&#xff0c;例如格式转换、图像大小调整、图像裁减、图像拼接、图像色彩校正和图像合成等常见的图像…

什么是线程?

线程 1. 线程概述 线程是计算机科学中的基本概念&#xff0c;指的是在一个进程中执行的独立指令流。通常&#xff0c;一个进程可以包含多个线程&#xff0c;它们共享进程的资源&#xff0c;如内存空间、文件句柄等&#xff0c;但每个线程有自己的独立执行流。线程是操作系统进…

档案统一管理的具体做法包括哪些?

档案统一管理工作&#xff0c;需要根据统管单位工作特点重建管理流程&#xff0c;优化和规范管理。档案统一管理的具体做法包括&#xff1a; 1. 设立档案管理部门或档案管理团队&#xff0c;负责统一管理机构的所有档案资料。 2. 建立档案管理制度和流程&#xff0c;明确档案的…

数字战场上的坚固屏障:雷池社区版(WAF)

黑客的挑战 智能语义分析算法&#xff1a; 黑客们常利用复杂技术进行攻击&#xff0c;但雷池社区版的智能语义分析算法能深入解析攻击本质&#xff0c;即使是最复杂的攻击手法也难以逃脱。 0day攻击防御&#xff1a; 传统防火墙难以防御未知攻击&#xff0c;但雷池社区版能有效…

【Linux Shell】5. 运算符

文章目录 【 1. 算术运算符 】1.1 expr 命令1.2 [ ] 方括号 【 2. 关系运算符 】【 3. 布尔运算符 】【 4. 逻辑运算符 】【 5. 字符串运算符 】【 6. 文件测试运算符 】 【 1. 算术运算符 】 运算符说明举例赋值a$b 把变量 b 的值赋给 a。 1.1 expr 命令 原生 bash 不支持简…

vue 自定义网页图标 favicon.ico 和 网页标题

效果预览 1. 添加配置 vue.config.js 在 module.exports { 内添加 // 自定义网页图标pwa: {iconPaths: {favicon32: "./favicon.ico",favicon16: "./favicon.ico",appleTouchIcon: "./favicon.ico",maskIcon: "./favicon.ico",msTil…

详解c++移动构造函数和移动赋值运算符在代码性能中的作用

对象移动 对象移动&#xff0c;就是把一个不想用了的对象A中的一些有用的数据提取出来&#xff0c;在构建新对象B的时候就不需要重新构建对象中的所有数据——从不想用了的对象A中提取出来的有用数据在构建对象B时都可以拿来使用。 我们知道&#xff0c;拷贝构造函数、拷贝赋…

openGauss学习笔记-193 openGauss 数据库运维-常见故障定位案例-备机卡住-数据库只读

文章目录 openGauss学习笔记-193 openGauss 数据库运维-常见故障定位案例-备机卡住-数据库只读193.1 switchover操作时&#xff0c;主机降备卡住193.1.1 问题现象193.1.2 原因分析193.1.3 处理办法 193.2 磁盘空间达到阈值&#xff0c;数据库只读193.2.1 问题现象193.2.2 原因分…

基于深度学习的果蔬检测识别系统(含UI界面、yolov5、Python代码、数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov5 yolov5主要包含以下几种创新&#xff1a;         1. 添加注意力机制&#xff08;SE、CBAM、CA等&#xff09;         2. 修改可变形卷积&#xff08;DySnake-主…

rocketmq查看消息堆积

第一种方式&#xff1a;命令行方式&#xff1a; //查看消费者情况&#xff0c;192.168.2.210为自己mq的地址&#xff0c;回显的Diff Total参数就是堆积的消息数量 ./mqadmin consumerProgress -n 192.168.2.210:9876第二种方式&#xff0c;通过控制台&#xff1a; 回显中的Di…

一点一点,照亮你的美

一、实验要求 当鼠标点击屏幕时&#xff0c;随机出现大大小小的星星闪烁&#xff0c;犹如夜晚的星空 二、实验思路 设置图片的大小 设置事件&#xff08;当鼠标点一下&#xff0c;获取一张图片&#xff09; 设置图片的位置 设置鼠标的位置和图片的相对位置 设置随机大小 …

AI老照片修复-Bringing-Old-Photos-Back-to-Life

&#x1f3e1; 个人主页&#xff1a;IT贫道-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;私聊博主加WX好友&#xff0c;获取更多资料哦~ &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. AI老照片修复原理-…