详解Java8 CompletableFuture的并行处理用法

 更新时间:2022年04月27日 14:18:13   作者:Java分享客栈  
Java8中有一个工具非常有用,那就是CompletableFuture,本章主要讲解CompletableFuture的并行处理用法,感兴趣的小伙伴可以了解一下

前言

工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回。

还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次feign去调用其他服务的方法获取数据,最后拿到想要的值并封装返回给前端。

这样的场景下,当某个或多个rpc调用的方法比较耗时,整个接口的响应就会非常慢。Java8之后,有一个工具非常适合处理这种场景,就是CompletableFuture。

场景

本章主要讲解CompletableFuture的并行处理用法,来针对这种很常见的场景,帮助大家快速掌握并应用到实际工作当中。CompletableFuture内部的用法还有许多,但个人用到的场景大多都是并行处理,对其他场景感兴趣的小伙伴可以另行百度搜索。

场景说明:

写一个接口,调用另外两个HTTP接口,分别获取二十四节气和星座,最后放在一起返回。

用法

1、在线API

我们访问极速数据网站,注册一个账号,就可以免费使用里面的一些在线API,平均每天有100次免费机会,对于我这样经常本地做一些测试的人来说完全够用了。

这里,我使用了其中的查询二十四节气API,和查询星座API,后面会提供案例代码,也可以直接使用我的。

2、编写在线API查询

这里,我们在查询时,模拟耗时的情况。

1.查询二十四节气

package com.example.async.service;

import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 查询二十四节气的服务
 * </p>
 *
 * @author 福隆苑居士,公众号:【Java分享客栈】
 * @since 2022-04-26 15:25
 */
@Service
@Slf4j
public class TwentyFourService {

   public static final String APPKEY = "xxxxxx";// 你的appkey
   public static final String URL = "https://api.jisuapi.com/jieqi/query";

   public String getResult() {
       String url = URL + "?appkey=" + APPKEY;
       String result = HttpUtil.get(url);

       // 模拟耗时
       try {
          TimeUnit.SECONDS.sleep(5);
       } catch (Exception e) {
          log.error("[二十四节气]>>>> 异常: {}", e.getMessage(), e);
       }

       return result;
   }
   
}

2.查询星座

package com.example.async.service;

import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 查询星座的服务
 * </p>
 *
 * @author 福隆苑居士,公众号:【Java分享客栈】
 * @since 2022-04-26 15:25
 */
@Service
@Slf4j
public class ConstellationService {
   public static final String APPKEY = "xxxxxx";// 你的appkey
   public static final String URL = "https://api.jisuapi.com/astro/all";

   public String getResult() {

      String url = URL + "?appkey=" + APPKEY;
      String result = HttpUtil.get(url);

      // 模拟耗时
      try {
         TimeUnit.SECONDS.sleep(5);
      } catch (Exception e) {
         log.error("[星座]>>>> 异常: {}", e.getMessage(), e);
      }

      return result;
   }
   
}

3、编写查询服务

package com.example.async.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 查询服务
 * </p>
 *
 * @author 福隆苑居士,公众号:【Java分享客栈】
 * @since 2022-04-26 17:38
 */
@Service
@Slf4j
public class QueryService {
   private final TwentyFourService twentyFourService;
   private final ConstellationService constellationService;

   public QueryService(TwentyFourService twentyFourService, ConstellationService constellationService) {
      this.twentyFourService = twentyFourService;
      this.constellationService = constellationService;
   }

   /**
    * 同步返回结果
    * @return 结果
    */
   public Map<String, Object> query() {
      // 1、查询二十四节气
      String twentyFourResult = twentyFourService.getResult();

      // 2、查询星座
      String constellationResult = constellationService.getResult();

      // 3、返回
      Map<String, Object> map = new HashMap<>();
      map.put("twentyFourResult", twentyFourResult);
      map.put("constellationResult", constellationResult);
      return map;
   }
}

4、编写测试接口

这里,我们专门加上了耗时计算。

package com.example.async.controller;

import cn.hutool.core.date.TimeInterval;
import com.example.async.service.QueryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * <p>
 * 测试
 * </p>
 *
 * @author 福隆苑居士,公众号:【Java分享客栈】
 * @since 2022-04-26 17:35
 */
@RestController
@RequestMapping("/api")
@Slf4j
public class TestController {

   private final QueryService queryService;

   public TestController(QueryService queryService) {
      this.queryService = queryService;
   }

   /**
    * 同步查询
    * @return 结果
    */
   @GetMapping("/query")
   public ResponseEntity<Map<String, Object>> query() {
      // 计时
      final TimeInterval timer = new TimeInterval();
      timer.start();
      Map<String, Object> map = queryService.query();
      map.put("costTime", timer.intervalMs() + " ms");
      return ResponseEntity.ok().body(map);
   }
}

5、效果

可以看到,两个接口一共耗费了10秒左右才返回。

6、CompletableFuture并行查询

现在我们来使用CompletableFuture改造下接口,并行查询两个HTTP接口再返回。

package com.example.async.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * <p>
 * 查询服务
 * </p>
 *
 * @author 福隆苑居士,公众号:【Java分享客栈】
 * @since 2022-04-26 17:38
 */
@Service
@Slf4j
public class QueryService {
   private final TwentyFourService twentyFourService;
   private final ConstellationService constellationService;

   public QueryService(TwentyFourService twentyFourService, ConstellationService constellationService) {
      this.twentyFourService = twentyFourService;
      this.constellationService = constellationService;
   }

   /**
    * 异步返回结果
    * @return 结果
    */
   public Map<String, Object> queryAsync() {

      Map<String, Object> map = new HashMap<>();

      // 1、查询二十四节气
      CompletableFuture<String> twentyFourQuery = CompletableFuture.supplyAsync(twentyFourService::getResult);
      twentyFourQuery.thenAccept((result) -> {
         log.info("查询二十四节气结果:{}", result);
         map.put("twentyFourResult", result);
      }).exceptionally((e) -> {
         log.error("查询二十四节气异常: {}", e.getMessage(), e);
         map.put("twentyFourResult", "");
         return null;
      });

      // 2、查询星座
      CompletableFuture<String> constellationQuery = CompletableFuture.supplyAsync(constellationService::getResult);
      constellationQuery.thenAccept((result) -> {
         log.info("查询星座结果:{}", result);
         map.put("constellationResult", result);
      }).exceptionally((e) -> {
         log.error("查询星座异常: {}", e.getMessage(), e);
         map.put("constellationResult", "");
         return null;
      });

      // 3、allOf-两个查询必须都完成
      CompletableFuture<Void> allQuery = CompletableFuture.allOf(twentyFourQuery, constellationQuery);
      CompletableFuture<Map<String, Object>> future = allQuery.thenApply((result) -> {
         log.info("------------------ 全部查询都完成 ------------------ ");
         return map;
      }).exceptionally((e) -> {
         log.error(e.getMessage(), e);
         return null;
      });

      // 获取异步方法返回值
      // get()-内部抛出了异常需手动处理; join()-内部处理了异常无需手动处理,点进去一看便知。
      future.join();

      return map;
   }
}

7、编写测试接口

package com.example.async.controller;

import cn.hutool.core.date.TimeInterval;
import com.example.async.service.QueryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * <p>
 * 测试
 * </p>
 *
 * @author 福隆苑居士,公众号:【Java分享客栈】
 * @since 2022-04-26 17:35
 */
@RestController
@RequestMapping("/api")
@Slf4j
public class TestController {

   private final QueryService queryService;

   public TestController(QueryService queryService) {
      this.queryService = queryService;
   }

   /**
    * 异步查询
    * @return 结果
    */
   @GetMapping("/queryAsync")
   public ResponseEntity<Map<String, Object>> queryAsync() {
      // 计时
      final TimeInterval timer = new TimeInterval();
      timer.start();
      Map<String, Object> map = queryService.queryAsync();
      map.put("costTime", timer.intervalMs() + " ms");
      return ResponseEntity.ok().body(map);
   }
}

8、CompletableFuture效果

可以看到,时间缩短了一倍。

思考

如果在微服务中,有一个很复杂的业务需要远程调用5个第三方laji厂家的接口,每个接口假设都耗时5秒,使用CompletableFuture并行处理最终需要多久?

答案是肯定的,同步查询需要25秒左右,CompletableFuture并行处理还是5秒左右,也就是说,同一个接口中,调用的耗时接口越多,CompletableFuture优化的幅度就越大。

示例代码

可以下载我的完整示例代码本地按需测试,里面有我的极速数据API的key,省得自己注册账号了,每天免费就100次,先到先得哦。

到此这篇关于详解Java8 CompletableFuture的并行处理用法的文章就介绍到这了,更多相关Java8 CompletableFuture内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot中的404错误:原因、影响及解决策略

    SpringBoot中的404错误:原因、影响及解决策略

    本文详细介绍了SpringBoot中404错误的出现原因、影响以及处理策略,404错误常见于URL路径错误、控制器配置问题、静态资源配置错误、依赖缺失或版本不兼容、配置错误和服务器配置问题,解决方法包括检查URL路径、审查控制器配置、配置静态资源
    2025-02-02
  • Spring boot实现应用打包部署的示例

    Spring boot实现应用打包部署的示例

    本篇文章主要介绍了Spring boot实现应用打包部署的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • Java Springboot自动装配原理详解

    Java Springboot自动装配原理详解

    这篇文章主要介绍了详解SpringBoot自动配置原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-10-10
  • 怎样使用PowerMockito 测试静态方法

    怎样使用PowerMockito 测试静态方法

    这篇文章主要介绍了使用PowerMockito 测试静态的方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 详解Java8新特性Stream之list转map及问题解决

    详解Java8新特性Stream之list转map及问题解决

    这篇文章主要介绍了详解Java8新特性Stream之list转map及问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • 解决Eclipse配置Tomcat出现Cannot create a server using the selected type错误

    解决Eclipse配置Tomcat出现Cannot create a server using the selected

    这篇文章主要介绍了解决Eclipse配置Tomcat出现Cannot create a server using the selected type错误的相关资料,需要的朋友可以参考下
    2017-02-02
  • SpringBoot3.X配置OAuth的代码实践

    SpringBoot3.X配置OAuth的代码实践

    在进行Java后端技术框架版本升级时,特别是将SpringBoot从2.X升级到3.X,发现对OAuth的配置有大幅变更,新版本中删除了多个常用配置类,本文给大家介绍SpringBoot3.X配置OAuth的相关知识,感兴趣的朋友一起看看吧
    2024-09-09
  • springboot2.0如何通过fastdfs实现文件分布式上传

    springboot2.0如何通过fastdfs实现文件分布式上传

    这篇文章主要介绍了springboot2.0如何通过fastdfs实现文件分布式上传,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • java实现简单图片上传下载功能

    java实现简单图片上传下载功能

    这篇文章主要为大家详细介绍了java实现简单图片上传下载功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-10-10
  • Java程序运行之JDK,指令javac java解读

    Java程序运行之JDK,指令javac java解读

    这篇文章主要介绍了Java程序运行之JDK,指令javac java,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01

最新评论