全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-01-25_CompletableFuture还能这么玩

您的位置:首页 >> 新闻 >> 行业资讯

CompletableFuture还能这么玩 点击关注公众号,“技术干货”及时达! 前言当我决定写这篇关于 CompletableFuture 的文章时,脑海中浮现出无数个曾经被异步编程折磨得死去活来的瞬间。 所以我希望能够用通俗且有趣的方式,帮列位看官逐步掌握这个Java异步编程的终极武器:CompletableFuture。 同时这篇文章,会尽可能的将知识点切成碎片化,不用看到长文就头痛。 坦白说,这篇文章确实有点长!一口气读完还是有点费劲。所以,我决定把它拆分为两篇: 第一篇,也就是本文,主打基础和进阶,深入浅出。 第二篇,重点在高级特性上,比如多任务编排,还会围绕实战和性能优化展开讲讲,这是传送门。 无论你是Java新手还是资深开发,相信都能在这里找到值得学习的干货。 耐心看完,你一定有所收获。 正文回顾基础还记得刚接触Java多线程的时候,有多懵圈吗?Thread、Runnable、Callable...这些概念像脱缰的野马一样在脑海中狂奔。 后来,我们认识了Future,以为终于找到了异步编程的救星,结果发现... 这家伙好像也不太靠谱? Future接口的局限性Future接口就像是一个"只能查询、不能改变"的“未来”。你投递了一个任务,然后就只能无奈地等在那里问:"完成了吗?完成了吗?"(通过isDone())。要么就干脆死等着(get())。 FutureString future = executor.submit(() - { Thread.sleep(1000); return "我是结果"; }); // 只能在这傻等 String result = future.get(); // 被迫阻塞 这种方式有多不优雅?就像你点了外卖,但是: 无法告诉外卖员:"送到了给我打电话"没法和朋友说:"我的外卖到了请你吃"更无法设置规则:"如果超时了就取消订单"CompletableFuture是什么这时候,CompletableFuture闪亮登场!它就像是Future接口的升级版,不仅能完成Future的所有功能,还自带"异步回调"、"任务编排"等高级技能,彻底解决了传统Future的局限性。 CompletableFutureString future = CompletableFuture.supplyAsync(() - { return "我是结果"; }).thenApply(result - { return "处理一下结果:" + result; }).thenAccept(finalResult - { System.out.println("最终结果:" + finalResult); }); 看到没?这就像是给外卖配上了现代化的配送系统: 可以设置送达后的自动通知(回调机制)可以预设各种状态的处理方案(异常处理、条件判断)甚至可以和其他订单组合起来统一处理(任务编排)为什么要使用CompletableFuture说到这里,你可能会问:"Future不也能用吗?为什么非要用CompletableFuture?" 让我们来看个真实场景:假设你要做一个商品详情页,需要同时调用: 商品基本信息库存信息促销信息评价信息用传统的Future: FutureProductInfo productFuture = executor.submit(() - getProductInfo());FutureStock stockFuture = executor.submit(() - getStock());FuturePromotion promotionFuture = executor.submit(() - getPromotion());FutureComments commentsFuture = executor.submit(() - getComments()); // 然后就是一堆get()的等待...痛苦ProductInfo product = productFuture.get();Stock stock = stockFuture.get();// 继续等... 用CompletableFuture: CompletableFutureProductInfo productFuture = CompletableFuture.supplyAsync(() - getProductInfo()); CompletableFutureStock stockFuture = CompletableFuture.supplyAsync(() - getStock()); CompletableFuturePromotion promotionFuture = CompletableFuture.supplyAsync(() - getPromotion()); CompletableFutureComments commentsFuture = CompletableFuture.supplyAsync(() - getComments()); CompletableFuture.allOf(productFuture, stockFuture, promotionFuture, commentsFuture) .thenAccept(v - { // 所有数据都准备好了,开始组装页面 buildPage(productFuture.join(), stockFuture.join(), promotionFuture.join(), commentsFuture.join()); }); 看出区别了吗?CompletableFuture 就像是给你的代码配备了一个小管家: 不用你盯着看是否完成自动通知你结果已准备好可以设定各种后续处理方案多个任务的编排也变得异常简单所以说,如果你还在用传统的Future,那真的是在给自己找麻烦。现代化的异步编程,CompletableFuture 才是正确的打开方式! 2. 创建异步任务的花式方法这个话题让我想起了点外卖时选择支付方式的场景 —— 支付宝还是微信?选择困难症又犯了! supplyAsync vs runAsync的选择这两个方法就像双胞胎兄弟,长得像但性格完全不同: // supplyAsync:我做事靠谱,一定给你返回点什么 CompletableFutureString future1 = CompletableFuture.supplyAsync(() - { return "我是有结果的异步任务"; }); // runAsync:我比较佛系,不想给你返回任何东西 CompletableFutureVoid future2 = CompletableFuture.runAsync(() - { System.out.println("我只是默默地执行,不给你返回值"); }); 选择建议: 需要返回值的时候:用supplyAsync,就像点外卖必须要等待送餐小哥送来美食不需要返回值的时候:用runAsync,就像发朋友圈,发完就完事了,不需要等待结果自定义线程池的正确姿势默认的线程池好比是共享单车,小黄、小蓝、小绿,谁都可以用,但高峰期可能要等。 而自定义线程池就像是私家车,只要调校的足够好,想怎么开就怎么开! // 错误示范:这是一匹脱缰的野马! ExecutorService wrongPool = Executors.newFixedThreadPool(10); // 正确示范:这才是精心调教过的千里马 ThreadPoolExecutor rightPool = new ThreadPoolExecutor( 5, // 核心线程数(正式员工) 10, // 最大线程数(含临时工) 60L, // 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue(100), // 工作队列(候客区) new ThreadFactoryBuilder().setNameFormat("async-pool-%d").build(), // 线程工厂(员工登记处) new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(客满时的处理方案) ); // 使用自定义线程池 CompletableFutureString future = CompletableFuture.supplyAsync(() - { return "我是通过专属线程池执行的任务"; }, rightPool); 异步任务的取消和超时处理就像等外卖的时候,超过预期时间就想取消订单(还是建议耐心等一等,你永远不知道送餐小哥正在做什么伟大的事情)。CompletableFuture也支持这种"任性"的操作: CompletableFutureString future = CompletableFuture.supplyAsync(() - { // 模拟一个耗时操作 try { Thread.sleep(5000); } catch (InterruptedException e) { // 被中断时的处理 return "我被中断了!"; } return "正常完成"; }); // 设置超时 try { String result = future.get(3, TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true); // 超时就取消任务 System.out.println("等太久了,不等了!"); } // 更优雅的超时处理 future.completeOnTimeout("默认值", 3, TimeUnit.SECONDS) .thenAccept(result - System.out.println("最终结果:" + result)); // 或者配合orTimeout使用 future.orTimeout(3, TimeUnit.SECONDS) // 超时就抛异常 .exceptionally(ex - "超时默认值") .thenAccept(result - System.out.println("最终结果:" + result)); 小贴士: 取消任务时,cancel(true)表示允许中断正在执行的任务,cancel(false)表示仅取消还未执行的任务completeOnTimeout比直接使用get更优雅,因为它不会阻塞orTimeout适合那些超时必须处理的场景,比如支付操作或者换个例子,异步任务的超时控制就像餐厅的叫号系统——不能让顾客无限等待,要给出一个合理的预期时间。 如果超时了,要么给个替代方案(completeOnTimeout),要么直接请顾客重新取号(orTimeout)。 3. 链式调用的艺术CompletableFuture的链式调用就像是一条生产流水线,原材料经过层层加工,最终变成成品。 thenApply、thenAccept、thenRun的区别这三个方法像是流水线上的三种工人,各司其职: CompletableFuture.supplyAsync(() - "Hello") .thenApply(s - { // 我是加工工人,负责把材料加工后返回新成品 return s + " World"; }) .thenAccept(result - { // 我是检验工人,只负责验收,不返回东西 System.out.println("收到结果: " + result); }) .thenRun(() - { // 我是打扫工人,不关心之前的结果,只负责收尾工作 System.out.println("生产线工作完成,开始打扫"); }); 通过这个例子就能看明白各自的用途: thenApply:当你需要转换结果并继续传递时使用thenAccept:当你只需要处理结果,不需要返回值时使用thenRun:当你只需要执行一个操作,不需要使用结果时使用异步转换:thenApplyAsync的使用场景有时候,转换操作本身也很耗时,这时就需要用到thenApplyAsync: CompletableFuture.supplyAsync(() - { // 模拟获取用户信息 return "用户基础信息"; }).thenApplyAsync(info - { // 耗时的处理操作,在新的线程中执行 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return info + " + 附加信息"; }, customExecutor); // 可以指定自己的线程池 组合多个异步操作:thenCompose vs thenCombine这两个方法其实就是两种不同的协作模式,一个串行,一个并行: thenCompose- 串行操作(一个接一个):CompletableFutureString getUserEmail(String userId) { return CompletableFuture.supplyAsync(() - "user@example.com"); } CompletableFutureString future = CompletableFuture .supplyAsync(() - "userId") .thenCompose(userId - getUserEmail(userId)); // 基于第一个结果去获取邮箱 thenCombine- 并行操作(两个一起做):CompletableFutureString future1 = CompletableFuture.supplyAsync(() - "价格信息"); CompletableFutureString future2 = CompletableFuture.supplyAsync(() - "库存信息"); CompletableFutureString result = future1.thenCombine(future2, (price, stock) - { // 同时处理价格和库存信息 return String.format("价格: %s, 库存: %s", price, stock); }); 使用建议: 当一个异步操作依赖另一个操作的结果时,使用thenCompose当两个异步操作相互独立,但最终结果需要组合时,使用thenCombine在结合一个实际案例,比如商品详情页的数据聚合 public CompletableFutureProductDetails getProductDetails(String productId) { CompletableFutureProduct productFuture = getProduct(productId); return productFuture.thenCompose(product - { // 基于商品信息获取促销信息 CompletableFuturePromotion promotionFuture = getPromotion(product.getCategory()); // 同时获取评论信息 CompletableFutureReviews reviewsFuture = getReviews(productId); // 组合促销和评论信息 return promotionFuture.thenCombine(reviewsFuture, (promotion, reviews) - { return new ProductDetails(product, promotion, reviews); }); }); } 4. 异常处理的技巧异常处理其实就是安全气囊 —— 不是每天都用得到,但关键时刻能救命。 应急的exceptionallyexceptionally可以理解成一个应急预案,当主流程出现问题时,它会提供一个替代方案: CompletableFutureString future = CompletableFuture .supplyAsync(() - { if (Math.random() 0.5) { throw new RuntimeException("服务暂时不可用"); } return "正常返回的数据"; }) .exceptionally(throwable - { // 记录异常日志 log.error("操作失败", throwable); // 返回默认值 return "服务异常,返回默认数据"; }); 也可以区分异常类型,进行针对性的处理: CompletableFutureString future = CompletableFuture .supplyAsync(() - callExternalService()) .exceptionally(throwable - { if (throwable.getCause() instanceof TimeoutException) { return "服务超时,返回缓存数据"; } else if (throwable.getCause() instanceof IllegalArgumentException) { return "参数异常,返回空结果"; } return "其他异常,返回默认值"; }); 两全其美的handlehandle方法比exceptionally更强大,在于它能同时处理正常结果和异常情况: CompletableFutureString future = CompletableFuture .supplyAsync(() - { if (Math.random() 0.5) { throw new RuntimeException("模拟服务异常"); } return "原始数据"; }) .handle((result, throwable) - { if (throwable != null) { log.error("处理异常", throwable); return "发生异常,返回备用数据"; } return result + " - 正常处理完成"; }); 举个比较常见的例子,处理订单: public CompletableFutureOrderResult processOrder(Order order) { return CompletableFuture .supplyAsync(() - validateOrder(order)) .thenApply(validOrder - processPayment(validOrder)) .handle((paymentResult, throwable) - { if (throwable != null) { // 支付过程中出现异常 if (throwable.getCause() instanceof PaymentDeclinedException) { return new OrderResult(OrderStatus.PAYMENT_FAILED, "支付被拒绝"); } else if (throwable.getCause() instanceof SystemException) { // 触发补偿机制 compensateOrder(order); return new OrderResult(OrderStatus.SYSTEM_ERROR, "系统异常"); } return new OrderResult(OrderStatus.UNKNOWN_ERROR, "未知错误"); } // 正常完成支付 return new OrderResult(OrderStatus.SUCCESS, paymentResult); }); } 使用建议 优先使用 exceptionally需要在处理结果的同时执行一些附加操作(如记录日志、发送指标等),使用handle更合适whenCompletewhenComplete和 handle 看起来很像,但用途不同,看代码和注释就明白了: // whenComplete:只是旁观者,不能修改结果 CompletableFutureString future1 = CompletableFuture .supplyAsync(() - "原始数据") .whenComplete((result, throwable) - { // 只能查看结果,无法修改 if (throwable != null) { log.error("发生异常", throwable); } else { log.info("处理完成: {}", result); } }); // handle:既是参与者又是修改者 CompletableFutureString future2 = CompletableFuture .supplyAsync(() - "原始数据") .handle((result, throwable) - { // 可以根据结果或异常,返回新的值 if (throwable != null) { return "异常情况下的替代数据"; } return result + " - 已处理"; }); 小贴士 使用whenComplete:当你只需要记录日志或执行一些清理工作,不需要改变结果时使用handle:当你需要在异常发生时返回备用值,或者需要转换成正常的结果时使用exceptionally:当你只关心异常情况,并且只需要提供一个替代值时希望永远也用不到这些异常处理的技巧,谁说不是呢~ 结尾第一篇主打基础操作,后面第二篇上重菜:任务编排、实战技巧、性能优化等等,当然还有喜闻乐见的虚拟线程。 那么,敬请期待! 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2025-01-10_闫妮微醺创意短片、苹果蛇年logo…|案例一周 下一篇:2024-01-31_今年看哭我的苹果新春大片《小蒜头》,是B站UP主先发的

TAG标签:

15
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价