【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

前言

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

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

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

场景

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

场景说明:

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

用法

1、在线API

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

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

【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

2、编写在线API查询

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

1)、查询二十四节气
package com.example.async.service;

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

/**
 *
 * 查询二十四节气的服务
 *
 *
 * @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;

/**
 *
 * 查询星座的服务
 *
 *
 * @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;

/**
 *
 * 查询服务
 *
 *
 * @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 query() {
      // 1、查询二十四节气
      String twentyFourResult = twentyFourService.getResult();

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

      // 3、返回
      Map 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;

/**
 *
 * 测试
 *
 *
 * @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> query() {
      // 计时
      final TimeInterval timer = new TimeInterval();
      timer.start();
      Map map = queryService.query();
      map.put("costTime", timer.intervalMs() + " ms");
      return ResponseEntity.ok().body(map);
   }
}

5、效果

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

【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

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;

/**
 *
 * 查询服务
 *
 *
 * @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 queryAsync() {

      Map map = new HashMap<>();

      // 1、查询二十四节气
      CompletableFuture 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 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 allQuery = CompletableFuture.allOf(twentyFourQuery, constellationQuery);
      CompletableFuture> 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>
 * &#x6D4B;&#x8BD5;
 * </p>
 *
 * @author &#x798F;&#x9686;&#x82D1;&#x5C45;&#x58EB;&#xFF0C;&#x516C;&#x4F17;&#x53F7;&#xFF1A;&#x3010;Java&#x5206;&#x4EAB;&#x5BA2;&#x6808;&#x3011;
 * @since 2022-04-26 17:35
 */
@RestController
@RequestMapping("/api")
@Slf4j
public class TestController {

   private final QueryService queryService;

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

   /**
    * &#x5F02;&#x6B65;&#x67E5;&#x8BE2;
    * @return &#x7ED3;&#x679C;
    */
   @GetMapping("/queryAsync")
   public ResponseEntity<map<string, object>> queryAsync() {
      // &#x8BA1;&#x65F6;
      final TimeInterval timer = new TimeInterval();
      timer.start();
      Map<string, object> map = queryService.queryAsync();
      map.put("costTime", timer.intervalMs() + " ms");
      return ResponseEntity.ok().body(map);
   }
}
</string,></map<string,>

8、CompletableFuture效果

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

【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

9、思考

如果在微服务中,有一个很复杂的业务需要远程调用5个第三方laji厂家的接口,每个接口假设都耗时5秒,使用CompletableFuture并行处理最终需要多久?
答案是肯定的,同步查询需要25秒左右,CompletableFuture并行处理还是5秒左右,也就是说,同一个接口中,调用的耗时接口越多,CompletableFuture优化的幅度就越大。

示例代码

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

链接:https://pan.baidu.com/doc/share/P_Jn_x22fos0ED3YEnqI8A-232386145447394
提取码:piil

觉得有帮助的话,就请顺手点个【推荐】吧,本人定期分享工作中的经验及趣事,原创文章均为手打,喜欢的话也可以关注一下哦~

Original: https://www.cnblogs.com/fulongyuanjushi/p/16198440.html
Author: 程序员济颠
Title: 【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/593035/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • java实现聊天,服务端与客户端代码(UDP)-狂神改

    首先是文件结构: 最后run的是下面两个 代码用的狂神的,不过他写的有点小bug,比如传信息会出现一堆空格(recieve data那里长度不应该用data.lenth()而应该用…

    Java 2023年6月5日
    0114
  • [转]SpringMVC+ Mybatis 配置多数据源 + 手动切换数据源

    正确可行的解决方法:使用Spring提供的AbstractRoutingDataSource类来根据请求路由到不同的数据源。具体做法是先设置两个不同的dataSource代表不同的…

    Java 2023年5月29日
    079
  • Java: State Pattern

    java;gutter:true; /<em><em> * 版权所有 2022 涂聚文有限公司 * 许可信息查看: * 描述: * 状态模式 State P…

    Java 2023年6月16日
    069
  • 深入Java微服务之网关系列1:什么是网关

    ​ 前言 近来,在想着重构一个新的产品。准备采用微服务的技术解决方案,来搭建基础设施框架。网关,是一个必不可少的组件。那么,网关到底是什么? 其又有什么特点或者特性,成为微服务必不…

    Java 2023年5月29日
    086
  • 多线程(线程组【ThreadGroup】)

    线程组: 把多个线程组合到一起。* 它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。 生产者和消费者之等待唤醒机制的代码优化 线程实现类—设置和获取…

    Java 2023年6月5日
    079
  • 以逗号分割的字符串和数组之间来回转换的方法

    数组转字符串用逗号分割 String[] arr = [“1″,”2″,”3″,”4&#8243…

    Java 2023年6月14日
    078
  • DispatcherServlet 分发流程

    0 太长不看版 HTTPServlet 的 Service 方法将请求按类进行分解 主要是根据HTTP方法的类型调用 doXXX 方法 GET 和 HEAD 方法需要对 if-mo…

    Java 2023年6月9日
    0105
  • QThread停止线程

    1 强制停止线程,停止使用run函数启动的线程。 if (m_td != NULL){m_td->terminate();m_td->wait(); // 调用wait…

    Java 2023年5月30日
    092
  • commons-collections1链分析

    概述 Commons Collections增强了Java集合框架。 它提供了几个功能来简化收集处理。 它提供了许多新的接口,实现和实用程序。在反序列化里,cc1这里主要就是Tra…

    Java 2023年6月6日
    092
  • 线程从2022-06-23 18.21 等待到2022-06-24 11:00

    "task-scheduler-4" #412 prio=5 os_prio=0 tid=0x00007fc5c0017800 nid=0x55d4 in Ob…

    Java 2023年6月9日
    099
  • 【Javafx】以树状列表的形式显示类(TreeTableView控件的使用)

    在使用Javafx插件开发作业项目时,我需要将房屋以树状表格的形式显示出来。实现的效果: 1、简单介绍 在这里简单介绍一下我的程序中涉及到的类的属性。 在我的程序中,需要显示的类为…

    Java 2023年6月8日
    093
  • Mybatis架构与原理

    一、简介 MyBatis 是一款优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集 Mybatis可以将Sql语句配置在XML文件中,避…

    Java 2023年5月30日
    091
  • 程序人生|从网瘾少年到微软、BAT、字节offer收割机逆袭之路

    有情怀,有干货,微信搜索【 三太子敖丙】关注这个不一样的程序员。本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料…

    Java 2023年6月9日
    096
  • 继承

    在旧事物的前提下,让新事物保持旧事物的特性,并泛生出新事物自己独有的特性。在java中,继承是对某一批类的抽象,提高了代码的复用性。 继承就是在A类(基类)上,泛生出B类(派生类)…

    Java 2023年6月5日
    079
  • 并查集路径压缩

    并查集里的 find 函数里可以进行路径压缩,是为了更快速的查找一个点的根节点。对于一个集合树来说,它的根节点下面可以依附着许多的节点,因此,我们可以尝试在 find 的过程中,从…

    Java 2023年6月5日
    0105
  • Java之收集很好的Java学习资料地址+博客

    https://blog.insanecoder.top/tcp-packet-splice-and-split-issue/ http://blog.csdn.net/qilix…

    Java 2023年5月29日
    079
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球