CompletableFuture使用样例

迈不过友情╰ 2024-04-01 16:15 188阅读 0赞

CompletableFuture使用样例

  • 前言
  • 一、故事
  • 二、使用前用到的api
  • 三、直接上代码
  • 总结

前言

CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,完美弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力。借助这项能力,可以轻松地组织不同任务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力# 系列文章目录


一、故事

  • 老爷和两个管家
  • 老爷想喝茶
  • 让两个管家协助
  • 一个烧水
  • 一个选茶叶
  • 自己去上厕所
  • 回来就可以喝茶
  • 岂不乐哉???

二、使用前用到的api

  • CompletableFuture 默认使用的是内部线程池,可自定义线程池使用
  • CompletableFuture.supplyAsync 创建异步调用任务
  • future.thenAccept 执行完成后通知
    表示第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。
  • thenRun/thenRunAsync
    通俗点讲就是,做完第一个任务后,再做第二个任务,第二个任务也没有返回值
  • CompletableFuture.allOf 编排异步任务 - 全部成功后通知thenAccept, 类似创建一个异步监听
  • future.join() 主线程会等待
  • supplyAsync 执行任务,支持返回值。
  • runAsync 执行任务,没有返回值。

三、直接上代码

管家需要做的两件事

  1. @Service
  2. public class TeaService {
  3. //烧茶水
  4. public Integer heatUpWater(){
  5. try {
  6. Thread.sleep(5000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. return 20;
  11. }
  12. //挑选茶
  13. public Double getTeas(){
  14. try {
  15. Thread.sleep(1000);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. return 20D;
  20. }
  21. }

老爷的吩咐

  1. @Service
  2. @Slf4j
  3. public class QueryService {
  4. @Autowired
  5. private TeaService teaService;
  6. public Map<String,Object> queryGoodsInfo(){
  7. Map<String, Object> res = new HashMap<>();
  8. //异步执行任务 烧水
  9. log.info("老李,去帮我把水烧一下,一会我要喝茶");
  10. CompletableFuture<Integer> numFuture = CompletableFuture.supplyAsync(() -> teaService.heatUpWater());
  11. numFuture.thenAccept((result) -> {
  12. log.info("老爷水烧好了,花了{}分钟",result);
  13. res.put("烧水时间", result);
  14. }).exceptionally((e) ->{
  15. log.error("锅烧炸了: {}", e.getMessage(), e);
  16. res.put("烧水时间", null);
  17. return null;
  18. });
  19. //异步执行任务 挑选茶叶
  20. log.info("小蓉,去帮我挑选一下茶叶");
  21. CompletableFuture<Double> priceFuture = CompletableFuture.supplyAsync(() -> teaService.getTeas());
  22. priceFuture.thenAccept((result) ->{
  23. log.info("老爷,茶叶挑选好了,先试试{}克的碧螺春",result);
  24. res.put("挑选茶叶时间", result);
  25. }).exceptionally((e) ->{
  26. log.error("茶被老鼠吃了: {}", e.getMessage(), e);
  27. res.put("挑选茶叶时间", null);
  28. return null;
  29. });
  30. //编排任务allOf 全部等待完成
  31. CompletableFuture<Void> allQuery = CompletableFuture
  32. .allOf(numFuture, priceFuture);
  33. //执行完成后回调
  34. CompletableFuture<Map<String, Object>> future = allQuery.thenApply((result) -> {
  35. log.info("------------------ 老爷不放心,看一下监控,发现都弄好了 ------------------ ");
  36. return res;
  37. }).exceptionally((e) -> {
  38. log.error(e.getMessage(), e);
  39. return null;
  40. });
  41. log.info("我去上个厕所,弄好了给我发消息,一会回来喝茶");
  42. // --这里主线程会等待异步线程全部执行完毕
  43. // --去掉future.join() 主线程不等待
  44. future.join();
  45. log.info("开始喝茶...");
  46. return res;
  47. }
  48. }

老爷的任务开始执行

  1. @Controller
  2. @Slf4j
  3. @RequestMapping("/completable")
  4. public class CompletableFutureController {
  5. @Autowired
  6. private QueryService queryService;
  7. @RequestMapping("get")
  8. @ResponseBody
  9. public Map<String,Object> get(){
  10. return queryService.queryGoodsInfo();
  11. }
  12. }

老爷任务执行结果

  1. [nio-8080-exec-2] c.e.w.completableFuture.QueryService : 老李,去帮我把水烧一下,一会我要喝茶
  2. [nio-8080-exec-2] c.e.w.completableFuture.QueryService : 小蓉,去帮我挑选一下茶叶
  3. [nio-8080-exec-2] c.e.w.completableFuture.QueryService : 我去上个厕所,弄好了给我发消息,一会回来喝茶
  4. [onPool-worker-2] c.e.w.completableFuture.QueryService : 老爷,茶叶挑选好了,先试试20.0克的碧螺春
  5. [onPool-worker-1] c.e.w.completableFuture.QueryService : ------------------ 老爷不放心,看一下监控,发现都弄好了 ------------------
  6. [onPool-worker-1] c.e.w.completableFuture.QueryService : 老爷水烧好了,花了20分钟
  7. [nio-8080-exec-2] c.e.w.completableFuture.QueryService : 开始喝茶...

总结

  • 不建议使用默认线程池

CompletableFuture代码中又使用了默认的「ForkJoin线程池」,处理的线程个数是电脑「CPU核数-1」。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。

  • CompletableFuture 功能很多,异步任务编排很灵活,有很多值得学习,有时间可以看看学习下,一定能解决工作中的很多问题
  • 获取返回值

join()和get()方法都是用来获取CompletableFuture异步之后的返回值
get 会有检查异常,需要手动处理(try catch)
join 方法抛出的是uncheck异常(即RuntimeException),不会强制开发者抛出,处理

  • get和join 都是阻塞的方法

如果使用它来获取异步调用的返回值,需要添加超时时间。
CompletableFuture.get(5, TimeUnit.SECONDS);

发表评论

表情:
评论列表 (有 0 条评论,188人围观)

还没有评论,来说两句吧...

相关阅读