SpringBoot項(xiàng)目中異步調(diào)用接口方式知多少?
環(huán)境:springboot2.3.9.RELEASE
經(jīng)常會(huì)遇到在項(xiàng)目中調(diào)用第三方接口的情景,你是如何調(diào)用的呢?同步?異步?
場(chǎng)景:
假設(shè)下單業(yè)務(wù)流程如下步驟:
1、查詢用戶信息。
2、查詢庫存信息。
3、查詢活動(dòng)信息(折扣)。
1.同步順序調(diào)用
- 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}) ;
- // 這里合并請(qǐng)求結(jié)果處理
- System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ;
- System.out.println("傳統(tǒng)方式耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;
- return true ;
- }
- @GetMapping("/create")
- public Object create() {
- return os.createOrder() ;
- }
調(diào)用結(jié)果:

接口一個(gè)一個(gè)調(diào)用,非常耗時(shí)。
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() ;
- // 這里合并請(qǐng)求結(jié)果處理
- System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ;
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- }
- System.out.println("多線程方式耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;
- return true ;
- }
調(diào)用結(jié)果:

這次耗時(shí)少了,性能明顯提升了。但在項(xiàng)目中我們一般是禁止直接創(chuàng)建線程的,如果這是個(gè)高并發(fā)的接口,那么我們的程序很可能出現(xiàn)OOM的錯(cuò)誤。
3.線程池(Callable+Future)防止內(nèi)存溢出風(fēng)險(xiǎn)
- 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("線程池方式耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;
- return true ;
- }
調(diào)用結(jié)果:

耗時(shí)和上一個(gè)基本一致,通過Future的方式有一個(gè)問題就是只能一個(gè)一個(gè)的取值,只有當(dāng)前的返回?cái)?shù)據(jù)了后才會(huì)繼續(xù)往下執(zhí)行。如果有其它的任務(wù)執(zhí)行完,那沒有輪到它也必須等待。
4.CompletionService(異步任務(wù)與使用已完成任務(wù)的結(jié)果分離),submit提交任務(wù),take獲取已經(jīng)完成的任務(wù),不用按照submit的順序獲取結(jié)果。
- 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方式耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;
- return true ;
- }
調(diào)用結(jié)果:

通過CompletionService方式不管任務(wù)添加的順序是什么,只要通過take方法就能獲取執(zhí)行完的結(jié)果,如果沒有任務(wù)執(zhí)行完,take方法會(huì)阻塞。
5.CompletableFuture(異步任務(wù)編排),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方式耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;
- return true ;
- }
調(diào)用結(jié)果:

CompletableFuture提供了非常強(qiáng)大的異步編程方法,可同步,可異步,可編排任務(wù)執(zhí)行,異步通過回調(diào)的方式執(zhí)行。該對(duì)象很多的一些方法與前端JavaScript中的Promise對(duì)象有點(diǎn)相像。