详解Java8 CompletableFuture的并行处理用法

目录
  • 前言
  • 场景
  • 用法
    • 1、在线API
    • 2、编写在线API查询
    • 3、编写查询服务
    • 4、编写测试接口
    • 5、效果
    • 6、CompletableFuture并行查询
    • 7、编写测试接口
    • 8、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优化的幅度就越大。

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

(0)

相关推荐

  • Java 8 的异步编程利器 CompletableFuture的实例详解

    目录 一个例子回顾Future 一个例子走进CompletableFuture CompletableFuture使用场景 创建异步任务 supplyAsync方法 runAsync方法 任务异步回调 1.thenRun/thenRunAsync 2.thenAccept/thenAcceptAsync 3.thenApply/thenApplyAsync 4.exceptionally 5.whenComplete方法 6.handle方法 多个任务组合处理 AND组合关系 OR组合的关系 区

  • Java8 CompletableFuture详解

    Java 8来了,是时候学一下新的东西了.Java 7和Java 6只不过是稍作修改的版本,而Java 8将会发生重大的改进.或许是Java 8太大了吧?今天我会给你彻底地解释JDK 8中的新的抽象 – CompletableFuture.众所周知,Java 8不到一年就会发布,因此这篇文章是基于JDK 8 build 88 with lambda support的.CompletableFuture extends Future提供了方法,一元操作符和促进异步性以及事件驱动编程模型,它并不止步

  • Java8中CompletableFuture使用场景与实现原理

    目录 1.概述 2.为什么引入CompletableFuture 3.功能 3.源码追踪 4.总结 1.概述 CompletableFuture是jdk1.8引入的实现类.扩展了Future和CompletionStage,是一个可以在任务完成阶段触发一些操作Future.简单的来讲就是可以实现异步回调. 2.为什么引入CompletableFuture 对于jdk1.5的Future,虽然提供了异步处理任务的能力,但是获取结果的方式很不优雅,还是需要通过阻塞(或者轮训)的方式.如何避免阻塞呢?

  • Java CompletableFuture的使用详解

    CompletableFuture​ 它代表某个同步或异步计算的一个阶段.你可以把它理解为是一个为了产生有价值最终结果的计算的流水线上的一个单元.这意味着多个指令可以链接起来从而一个阶段的完成可以触发下一个阶段的执行. 任务开启 supplyAsync 开启一个子线程去执行有返回结果 开启一个子线程用来执行执行事务,可以通过返回值的join来得到返回值. 例如: print("去煮饭了"); CompletableFuture<String> completableFutu

  • Java8中CompletableFuture的用法全解

    目录 前言 一.创建异步任务 1.Future.submit 2.supplyAsync / runAsync 二.异步回调 1.thenApply / thenApplyAsync 2.thenAccept / thenRun 3. exceptionally 4.whenComplete 5.handle 三.组合处理 1.thenCombine / thenAcceptBoth / runAfterBoth 2.applyToEither / acceptEither / runAfter

  • Java8 CompletableFuture 异步执行操作

    目录 1.简介 2.异步执行 3.守护线程 4.处理执行结果 1.简介 CompletableFuture 是 JDK8 提供的一个异步执行工具. 示例1: public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { for (int i

  • 详解Java8 CompletableFuture的并行处理用法

    目录 前言 场景 用法 1.在线API 2.编写在线API查询 3.编写查询服务 4.编写测试接口 5.效果 6.CompletableFuture并行查询 7.编写测试接口 8.CompletableFuture效果 思考 前言 工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回. 还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次feign去调用其他服务的方法获取数据,最后拿到想要的值并封装返回给前端. 这样的场

  • 详解Java8中Optional的常见用法

    目录 一. 简介 二.Java8 之前,空指针异常判断 三.Optional的使用 1.创建Optional实例 2.访问 Optional 对象的值 3.返回默认值 4.返回异常 (常用) 5.转换值 6.过滤值 一. 简介 Opitonal是java8引入的一个新类,目的是为了解决空指针异常问题.本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空. Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现.但是 Optional

  • 详解Java8 Collect收集Stream的方法

    Collection, Collections, collect, Collector, Collectos Collection是Java集合的祖先接口. Collections是java.util包下的一个工具类,内涵各种处理集合的静态方法. java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一个函数,负责收集流. java.util.stream.Collector 是一个收

  • 详解Java8与Runtime.getRuntime().availableProcessors()

    lambda表达式以及并行流.官方承诺你写出来的代码更运行得更快.流会自动通过Fork/Join池并行地执行.我听过一些关于Java 8的主题的演讲,不过在这个非常关键的点上它们都说的有点问题.我计划在后续的文章中对并行流进行下深入的讲解,在这之前我先花点时间仔细地分析下它.关于这个问题,我只想问你们一个非常简单的问题,不过也是一个非常重要的问题,因为它是很多问题的关键所在.这个问题是: 这些并行操作的线程都是从哪来的? 在Java 8里,我们有一个通用的Fork/Join池,我们可以通过For

  • 详解JAVA8 函数式接口

    写在前面 Java8中内置了一些在开发中常用的函数式接口,极大的提高了我们的开发效率.那么,问题来了,你知道都有哪些函数式接口吗? 函数式接口总览 这里,我使用表格的形式来简单说明下Java8中提供的函数式接口. 四大核心函数式接口 首先,我们来看四大核心函数式接口,如下所示. 函数式接口 参数类型 返回类型 使用场景 Consumer消费型接口 T void 对类型为T的对象应用操作,接口定义的方法:void accept(T t) Supplier供给型接口 无 T 返回类型为T的对象,接口

  • 详解Oracle游标的简易用法

    下面看下Oracle游标的简易用法,具体代码如下所示: create or replace procedure NW_DelYW(iOPERATION_ID number, sUserID varchar2) is sCurDJBH yw_operation_link.djbh%type; cursor table_yw(ywid yw_operation.id%type) is select * from yw_operation_link t1 where t1.operation_id =

  • Node.js API详解之 tty功能与用法实例分析

    本文实例讲述了Node.js API详解之 tty功能与用法.分享给大家供大家参考,具体如下: tty 可以理解为终端的意思.tty 模块提供终端相关的接口,用来获取终端的行数列数等. 通过 const tty = require('tty'); 的方式引用 tty 模块 process.stdout.isTTY 说明: 此属性位于 process 模块,用来判断 Node.js 是否运行在一个 TTY 环境中 demo: console.log(process.stdout.isTTY) //

  • 详解shell 变量的高级用法示例

    变量删除和替换 案例:从头开始匹配,将符合最短的数据删除 (#) variable_1="I love you, Do you love me" echo $variable_1 variable_2=${variable_1#*ov} echo $variable_2 案例:从头开始匹配,将复合最短的数据删除(##) varible_3=${variable_1##*ov} echo $varible_3 案例:替换字符串,只替换第一次匹配成功的(/) echo $PATH var6

  • 详解Java8中的Lambda表达式

    Lambda是什么 Lambda表达式,也可称为闭包,是java8的新特性,作用是取代大部分内部类,优化java代码结构,让代码变得更加简洁紧凑. Lambda的基本语法 (expression)->expression 或 (expression)->{statements;} Lambda最重要特点 用()->{}代码块替代匿名内部类 //(param)->expression;//(param)->statment;//(param)->{statments};/

  • 详解C# 中Session的用法

    Session模型简介 在学习之前我们会疑惑,Session是什么呢?简单来说就是服务器给客户端的一个编号.当一台WWW服务器运行时,可能有若干个用户浏览正在运正在这台服务器上的网站.当每 个用户首次与这台WWW服务器建立连接时,他就与这个服务器建立了一个Session,同时服务器会自动为其分配一个SessionID,用以标识这个用 户的唯一身份.这个SessionID是由WWW服务器随机产生的一个由24个字符组成的字符串,我们会在下面的实验中见到它的实际样子. 这个唯一的SessionID是有

随机推荐