SpringBoot项目中异步调用接口方式知多少?

开发 项目管理
经常会遇到在项目中调用第三方接口的情景,你是如何调用的呢?同步?异步?

[[388631]]

 环境:springboot2.3.9.RELEASE

经常会遇到在项目中调用第三方接口的情景,你是如何调用的呢?同步?异步?

场景:

假设下单业务流程如下步骤:

1、查询用户信息。

2、查询库存信息。

3、查询活动信息(折扣)。

1.同步顺序调用

public boolean createOrder() { 
        long start = System.currentTimeMillis() ; 
        String userResult = restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ; 
        String storageResult = restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ; 
        String discountResult = restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ; 
        // 这里合并请求结果处理 
        System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ; 
        System.out.println("传统方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ; 
        return true ; 
    } 
  @GetMapping("/create"
    public Object create() { 
        return os.createOrder() ; 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

 调用结果:


接口一个一个调用,非常耗时。

2.多线程(Callable+Future)

public boolean createOrder2() { 
        long start = System.currentTimeMillis() ; 
        Callable<String> userCallable = () -> { 
            return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ; 
        } ; 
        Callable<String> storageCallable = () -> { 
            return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ; 
        } ; 
        Callable<String> discountCallable = () -> { 
            return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ; 
        } ; 
        FutureTask<String> userTask = new FutureTask<>(userCallable) ; 
        FutureTask<String> storageTask = new FutureTask<>(storageCallable) ; 
        FutureTask<String> discountTask = new FutureTask<>(discountCallable) ; 
        new Thread(userTask).start() ; 
        new Thread(storageTask).start() ; 
        new Thread(discountTask).start() ; 
        try { 
            String userResult = userTask.get() ; 
            String storageResult = storageTask.get() ; 
            String discountResult = discountTask.get() ; 
            // 这里合并请求结果处理 
            System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ; 
        } catch (InterruptedException | ExecutionException e) { 
            e.printStackTrace(); 
        } 
        System.out.println("多线程方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ; 
        return true ; 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

 调用结果:


这次耗时少了,性能明显提升了。但在项目中我们一般是禁止直接创建线程的,如果这是个高并发的接口,那么我们的程序很可能出现OOM的错误。

3.线程池(Callable+Future)防止内存溢出风险

ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000)) ; 
    public boolean createOrder3() { 
        long start = System.currentTimeMillis() ; 
        List<Future<String>> results = new ArrayList<>(3) ; 
        results.add(pool.submit(() -> { 
            return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ; 
        })) ; 
        results.add(pool.submit(() -> { 
            return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ; 
        })) ; 
        results.add(pool.submit(() -> { 
            return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ; 
        })) ; 
        for (int i = 0, size = results.size(); i < size; i++) { 
            try { 
                System.out.println(results.get(i).get()) ; 
            } catch (InterruptedException | ExecutionException e) { 
                e.printStackTrace(); 
            } 
        } 
        System.out.println("线程池方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ; 
        return true ; 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

 调用结果:


耗时和上一个基本一致,通过Future的方式有一个问题就是只能一个一个的取值,只有当前的返回数据了后才会继续往下执行。如果有其它的任务执行完,那没有轮到它也必须等待。

4.CompletionService(异步任务与使用已完成任务的结果分离),submit提交任务,take获取已经完成的任务,不用按照submit的顺序获取结果。

public boolean createOrder4() { 
        long start = System.currentTimeMillis() ; 
        CompletionService<String> cs = new ExecutorCompletionService<>(pool) ; 
        cs.submit(() -> { 
            return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ; 
        }) ; 
        cs.submit(() -> { 
            return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ; 
        }) ; 
        cs.submit(() -> { 
            return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ; 
        }) ; 
        for (int i = 2 ; i >=0; i--) { 
            try { 
                System.out.println(cs.take().get()) ; 
            } catch (InterruptedException | ExecutionException e) { 
                e.printStackTrace(); 
            } 
        } 
        System.out.println("CompletionService方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ; 
        return true ; 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

 调用结果:


通过CompletionService方式不管任务添加的顺序是什么,只要通过take方法就能获取执行完的结果,如果没有任务执行完,take方法会阻塞。

5.CompletableFuture(异步任务编排),JDK1.8

public boolean createOrder5() { 
        long start = System.currentTimeMillis() ; 
        CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> { 
            return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ; 
        }) ; 
         
        CompletableFuture<String> storageFuture = CompletableFuture.supplyAsync(() -> { 
            return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ; 
        }) ; 
         
        CompletableFuture<String> discountFuture = CompletableFuture.supplyAsync(() -> { 
            return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}); 
        }) ; 
        CompletableFuture<List<String>> result = CompletableFuture 
                .allOf(userFuture, storageFuture, discountFuture) 
                .thenApply((Void) -> { 
                    List<String> datas = new ArrayList<>() ; 
                    try { 
                        datas.add(userFuture.get()) ; 
                        datas.add(storageFuture.get()) ; 
                        datas.add(discountFuture.get()) ; 
                    } catch (InterruptedException | ExecutionException e) { 
                        e.printStackTrace(); 
                    } 
                    return datas ; 
                }).exceptionally(e -> { 
                    e.printStackTrace() ; 
                    return null ; 
                }) ; 
        try { 
            System.out.println(result.get()) ; 
        } catch (InterruptedException | ExecutionException e1) { 
            e1.printStackTrace(); 
        } 
        System.out.println("CompletableFuture方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ; 
        return true ; 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

 调用结果:


CompletableFuture提供了非常强大的异步编程方法,可同步,可异步,可编排任务执行,异步通过回调的方式执行。该对象很多的一些方法与前端JavaScript中的Promise对象有点相像。

 

责任编辑:姜华 来源: 今日头条
相关推荐

2023-08-23 13:24:00

异步编程方法

2021-03-29 09:26:44

SpringBoot异步调用@Async

2021-12-10 07:47:30

Javascript异步编程

2010-03-01 14:01:50

WCF服务异步调用

2022-09-27 12:01:56

Spring异步调用方式

2009-10-20 16:48:30

C#委托

2009-12-21 14:10:26

WCF异步调用

2018-12-12 15:01:22

开源存储 软件

2024-10-15 10:28:43

2009-11-09 10:50:30

WCF异步调用

2021-03-30 10:46:42

SpringBoot计数器漏桶算法

2022-09-28 14:54:07

Spring注解方式线程池

2009-07-01 13:58:00

JavaScript异

2009-11-06 15:54:15

WCF异步调用

2009-07-01 14:31:01

JavaScript异

2009-07-01 14:23:46

JavaScript异

2010-02-22 13:28:05

WCF异步调用

2009-07-01 14:37:14

JavaScript异

2009-07-01 14:05:23

JavaScript异

2009-08-21 11:24:16

C#异步调用
点赞
收藏

51CTO技术栈公众号