springboot:使用非同步註解@Async獲取執行結果的坑

北漂程式設計師發表於2021-08-21

springboot:使用非同步註解@Async的那些坑

一、引言

在java後端開發中經常會碰到處理多個任務的情況,比如一個方法中要呼叫多個請求,然後把多個請求的結果合併後統一返回,一般情況下呼叫其他的請求一般都是同步的,也就是每個請求都是阻塞的,那麼這個處理時間必定是很長的,有沒有一種方法可以讓多個請求非同步處理那,答案是有的。

springboot中提供了很便利的方式可以解決上面的問題,那就是非同步註解@Async。正確的使用該註解可以使你的程式飛起,相反如果使用不當那麼並不會取到理想的效果。

二、獲取非同步執行結果

1、環境介紹

下面是我的controller,SyncController.java

package com.atssg.controller;

import com.atssg.service.MySyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@Slf4j
@RestController
@RequestMapping("/sync")
public class SyncController {
    @Autowired
    private MySyncService syncService;

    @GetMapping(value = "/test")

    public String test() {
        String str=null;
        try {

            log.info("start");
            str = syncService.asyncMethod();
            log.info("str:{}", str);
            return str;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return str;
    }

}

在controller中就是呼叫下層的方法並返回,再看service層的類MySyncService.java

package com.atssg.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@Service
public class MySyncService {
    @Autowired
    private SyncService syncService;

    /**
     * 非同步方法
     *
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    public String asyncMethod() throws InterruptedException, ExecutionException {

        Future<String> result1 = syncService.method1("I");
        Future<String> result2 = syncService.method2("love");
        Future<String> result3 = syncService.method3("async");

        String str = result1.get();
        String str2 = result2.get();
        String str3 = result3.get();

        String result = str + str2 + str3;

        return result;
    }

    /**
     * 同步方法
     *
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    public String syncMethod() throws InterruptedException, ExecutionException {
        /*同步寫法*/
        String str = syncService.method1("I").get();
        String str2 = syncService.method2("love").get();
        String str3 = syncService.method3("async").get();
        return str + str2 + str3;
    }


}

上面便是service類,僅僅是呼叫下次非同步層的方法,並取得返回值。上面類中有兩個方法,其寫法也類似但結果卻大不相同,後面詳說。

下面是非同步層的方法,SyncService.java

package com.atssg.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;

@Service
@Async
public class SyncService {

    //@Async
    public Future<String> method1(String str) throws InterruptedException {
       Thread.sleep(1000*10);
        return new AsyncResult<>( str);
    }
    //@Async
    public Future<String> method2(String str) throws InterruptedException {
        Thread.sleep(1000*5);
        return new AsyncResult<>(str);
    }
   // @Async
    public Future<String> method3(String str) throws InterruptedException {
        Thread.sleep(1000*15);
        return new AsyncResult<>(str);
    }


}

該類使用@Async註解,表明該類中所有的方法都是非同步執行的,其中@Async可修飾類也可以修飾方法。

這便是所有的環境。

2、錯誤的方式

在MySyncService中有兩個方法,先看其中一個方法

public String syncMethod() throws InterruptedException, ExecutionException {
        /*同步寫法*/
        String str = syncService.method1("I").get();
        String str2 = syncService.method2("love").get();
        String str3 = syncService.method3("async").get();
        return str + str2 + str3;
    }

這種寫法是呼叫非同步方法後立即呼叫get()方法,即獲取結果,下面看測試結果,在controllor中呼叫該方法,下面看執行結果

2021-08-21 11:06:28.612  INFO 3584 --- [nio-8080-exec-1] com.atssg.controller.SyncController      : start
2021-08-21 11:06:58.651  INFO 3584 --- [nio-8080-exec-1] com.atssg.controller.SyncController      : str:Iloveasync

可以看到共執行了30s,在非同步層的方法中的三個方法如下,

//@Async
    public Future<String> method1(String str) throws InterruptedException {
       Thread.sleep(1000*10);
        return new AsyncResult<>( str);
    }
    //@Async
    public Future<String> method2(String str) throws InterruptedException {
        Thread.sleep(1000*5);
        return new AsyncResult<>(str);
    }
   // @Async
    public Future<String> method3(String str) throws InterruptedException {
        Thread.sleep(1000*15);
        return new AsyncResult<>(str);
    }

可以看到這三個方法分別是睡眠10s、5s、15s,這就很好理解了syncMethod()方法中的寫法是同步的,未達到非同步的目的,切記呼叫完非同步方法進接著呼叫get()方法不是非同步的方式,而是同步的。

3、正確方式

上面看了錯誤的用法,下面看正確的方式,

 public String asyncMethod() throws InterruptedException, ExecutionException {

        Future<String> result1 = syncService.method1("I");
        Future<String> result2 = syncService.method2("love");
        Future<String> result3 = syncService.method3("async");

        String str = result1.get();
        String str2 = result2.get();
        String str3 = result3.get();

        String result = str + str2 + str3;

        return result;
    }

這種方式是首先呼叫非同步方法,然後分別呼叫get()方法,取得執行結果。下面看測試結果

2021-08-21 11:17:23.516  INFO 3248 --- [nio-8080-exec-1] com.atssg.controller.SyncController      : start
2021-08-21 11:17:38.535  INFO 3248 --- [nio-8080-exec-1] com.atssg.controller.SyncController      : str:Iloveasync

執行時間未15s,這就很好解釋了,非同步層的三個方法,分別睡眠的時間是10s、5s、15s,既然是非同步執行的,那麼總的執行時間肯定是三個方法中最長的那個,符合測試結果。這才@Async正確的開啟姿勢。

三、非同步執行@Async註解

@Async註解的定義如下,

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
    String value() default "";
}

可以看到該註解可以用在類及方法上,用在類上表示類中的所有方法都是非同步的,用在方法上表示該方法是非同步的。

四、總結

今天的文章分享到這裡,主要分享了關於@Async註解在獲取執行結果的時候的坑,一定要先呼叫非同步方法,然後再呼叫get()方法,獲取結果,其中get方法還有一個過載的,可以設定超時時間,即超過設定的超時時間便返回,不再等待,各位小夥伴可以自己試驗。

 V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

下次繼續分享有關@Async註解使用的一些小細節,歡迎持續關注。

推薦閱讀

[springboot:非同步呼叫@Async]:

image

相關文章