Java Spring註解之@Async的基本用法和示例

背景

通常,在Java中的方法調用都是同步調用,比如在A方法中調用瞭B方法,則在A調用B方法之後,必須等待B方法執行並返回後,A方法才可以繼續往下執行。這樣容易出現的一個問題就是如果B方法執行時間較長,則可能會導致調用A的請求響應遲緩,為瞭解決這種問題,可以使用Spirng的註解@Async來用異步調用的方式處理,當然也會有別的多線程方式解決此類問題,本文主要分析@Async在解決此類問題時的用法以及具體的示例。

異步調用

比如方法A調用方法B,如果B是一個異步方法,則A方法在調用B方法之後,不用等待B方法執行完成,而是直接往下繼續執行別的代碼。

@Async介紹

在Spring中,使用@Async標註某方法,可以使該方法變成異步方法,這些方法在被調用的時候,將會在獨立的線程中進行執行,調用者不需等待該方法執行完成。

在Spring中啟用@Async

使用@EnableAsync

@Slf4j
@SpringBootApplication
@ComponentScan(basePackages = {"com.kaesar.spring"})
@EnableAsync // 開啟異步調用
public class Application {
    public static void main(String[] args) {
        log.info("spring boot開始啟動...");
        ApplicationContext ctx = SpringApplication.run(Application.class, args);
        String[] activeProfiles = ctx.getEnvironment().getActiveProfiles();
        for (String profile : activeProfiles) {
            log.info("當前環境為:" + profile);
        }
        log.info("spring boot啟動成功...");
    }
}

示例一:基本使用方式

在方法上添加@Async註解

/**
 * 異步方法
 * 默認情況下,Spring 使用 SimpleAsyncTaskExecutor 去執行這些異步方法(此執行器沒有限制線程數)。
 * 此默認值可以從兩個層級進行覆蓋:
 * 方法級別
 * 應用級別
 */
@Async
public void test2() {
    try {
        log.info(Thread.currentThread().getName() + " in test2, before sleep.");
        Thread.sleep(2000);
        log.info(Thread.currentThread().getName() + " in test2, after sleep.");
    } catch (InterruptedException e) {
        log.error("sleep error.");
    }
}

調用異步方法

/**
 * 調用不同類的異步方法
 */
public void func1() {
    log.info("before call async function.");
    asyncService.test2();
    log.info("after call async function.");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        log.error("sleep error.");
    }
    log.info("func end.");
}

從執行結果可以看出,main線程中的func1方法在調用異步方法test2後,沒有等待test2方法執行完成,直接執行後面的代碼。

示例二:在同一個類中調用異步方法

方法func2和上面的異步方法test2方法在同一個類中

從執行結果可知,main線程中的func2方法在調用異步方法test2方法後,等待test2方法執行完後,才繼續往後執行。

示例三:異步方法是static方法

異步方法test3是一個static方法

/**
 * 異步方法不能是 static 方法,不然註解失效
 */
@Async
public static void test3() {
  try {
    log.info(Thread.currentThread().getName() + " in test3, before sleep.");
    Thread.sleep(2000);
    log.info(Thread.currentThread().getName() + " in test3, after sleep.");
  } catch (InterruptedException e) {
    log.error("sleep error.");
  }

}

調用test3的方法

/**
 * 調用不同類的異步方法,異步方法是 static 方法
 */
public void func3() {
  log.info(Thread.currentThread().getName() + ": before call async function.");
  AsyncService.test3();
  log.info(Thread.currentThread().getName() + ": after call async function.");
  try {
    Thread.sleep(3000);
  } catch (InterruptedException e) {
    log.error("sleep error.");
  }
  log.info(Thread.currentThread().getName() + ": func end.");
}

執行結果。可以看出在static方法上添加@Async註解,當調用該方法時並沒有新啟用一個線程單獨執行,而是按順序執行代碼,說明異步無效。

示例四:在方法級別上修改默認的執行器

自定義一個線程池執行器代替默認的執行器

自定義的線程池執行器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * 自定義線程池
 */
@Configuration
public class AsyncConfig {
    private static final int MAX_POOL_SIZE = 10;
    private static final int CORE_POOL_SIZE = 5;

    @Bean("asyncTaskExecutor")
    public AsyncTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
        asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        asyncTaskExecutor.setThreadNamePrefix("async-task-thread-pool-");
        asyncTaskExecutor.initialize();
        return asyncTaskExecutor;
    }
}

異步方法上使用自定義的執行器

/**
 * 在方法級別上修改默認的執行器
 */
@Async("asyncTaskExecutor")
public void test4() {
  try {
    log.info(Thread.currentThread().getName() + ": in test4, before sleep.");
    Thread.sleep(2000);
    log.info(Thread.currentThread().getName() + ": in test4, after sleep.");
  } catch (InterruptedException e) {
    log.error("sleep error.");
  }
}

調用test4異步方法

/**
 * 調用不同類的異步方法
 */
public void func4() {
  log.info(Thread.currentThread().getName() + ": before call async function.");
  asyncService.test4();
  log.info(Thread.currentThread().getName() + ": after call async function.");
  try {
    Thread.sleep(3000);
  } catch (InterruptedException e) {
    log.error("sleep error.");
  }
  log.info(Thread.currentThread().getName() + ": func end.");
}

從執行結果可以看出,@Async註解聲明使用指定的自定義的異步執行器,已經替換瞭默認的執行器。而且調用異步方法的main線程沒有等待異步方法的執行。

說明:新建自定義的執行器後,註解@Async默認就會替換成自定義的執行器,所以在@Async註解上可以不用指定。

\(1.01^{365} ≈ 37.7834343329\)
\(0.99^{365} ≈ 0.02551796445\)
相信堅持的力量!

補充:Java中異步註解@Async的陷阱

或許,你在Java後端添加異步過程時會這樣處理,然後搖搖大擺、灰溜溜地閃,而實際的運行結果卻並不是我們期望的那樣。那麼,現在就將試驗結果記錄如下,以便少走彎路。

(一)在Controller層的公開接口直接添加@Async註解

當前端調用該種接口時會立刻結束,意味著開始即結束,不會在乎該異步接口返回的數據,其實這種接口隻適合前端下發命令,後續就不管後端的處理流程瞭,也不需要後端返回的對象。

(二)在Controller層的私有接口直接添加@Async註解

這種情況是,前端調用後端的公開接口並等待該接口返回,此時在該接口中調用瞭該層的添加瞭@Async註解的私有方法,也許你期待的是讓後端接口立刻返回,讓具體的處理過程放在@Async註解的私有函數中,可事實並沒有達到你的效果,添加瞭@Async註解的私有函數依舊是同步過程,即使你在Controller層的類前面添加瞭@EnableAsync註解,也無濟於事;所以,這種方式達不到異步的效果。我們可以通過日志來驗證該過程,如下圖所示:

在上圖中,我們看到先進入Controller層公開接口,然後進入帶有@Async註解的私有方法,接著跳出,最後又回到Controller層公開接口,整個流程就是同步過程,此時的@Async註解沒有效果。

(三)在Service層的公開接口直接添加@Async註解

在Controller層提供同步流程的接口,隻是在該層中會調用Service層的異步接口,隻需要在需要用異步流程完成任務的接口上方添加@Async註解即可,這種策略是可以實現我們的異步過程的,我們還是通過日志來驗證該流程,如下圖所示:

在上圖中,我們看到流程首先進入Controller層,然後立即跳出瞭Controller層,而Service層的異步接口就是後續完成的任務瞭,這樣的流程已達到我們想要的異步過程瞭。

總結

到此這篇關於Java中@Async的基本用法和示例的文章就介紹到這瞭,更多相關java @Async的用法內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: