一 、@Async 的使用方式介紹
spring中用@Async注解標記的方法,稱為異步方法,它會在調用方的當前線程之外的獨立的線程中執行,其實就相當於我們自己 new Thread(() -> System.out.println("Hello world !")); 這樣在另一個線程中去執行相應的業務邏輯。本篇先只講@Async的使用,以后再分析它的實現原理。
@Async注解使用條件
1、@Async注解一般用在類的方法上,如果用在類上,那么這個類所有的方法都是異步執行的;
2、所使用的@Async注解方法的類對象應該是Spring容器管理的bean對象;
3、調用異步方法類上需要配置上注解@EnableAsync
使用注意:
1、默認情況下(即@EnableAsync注解的mode=AdviceMode.PROXY),同一個類內部沒有使用@Async注解修飾的方法調用@Async注解修飾的方法,是不會異步執行的,這點跟 @Transitional 注解類似,底層都是通過動態代理實現的。如果想實現類內部自調用也可以異步,則需要切換@EnableAsync注解的mode=AdviceMode.ASPECTJ,詳見@EnableAsync注解。
2、任意參數類型都是支持的,但是方法返回值必須是void或者Future類型。當使用Future時,你可以使用 實現了Future接口的ListenableFuture接口或者CompletableFuture類與異步任務做更好的交互。如果異步方法有返回值,沒有使用Future類型的話,調用方獲取不到返回值。
1、 了解 @Async
在 java 應用中,絕大多數情況下都是通過同步的方式來實現交互處理的;但是在處理與第三方系統交互的時候,容易造成響應遲緩的情況,之前大部分都是使用多線程來完成此類任務,其實,在spring 3.x之后,就已經內置了@Async來完美解決這個問題,本文將完成介紹@Async的用法。
2、 何為異步調用?
在解釋異步調用之前,我們先來看同步調用的定義;同步就是整個處理過程順序執行,當各個過程都執行完畢,並返回結果。 異步調用則是只是發送了調用的指令,調用者無需等待被調用的方法完全執行完畢;而是繼續執行下面的流程。
例如, 在某個調用中,需要順序調用 A, B, C三個過程方法;如他們都是同步調用,則需要將他們都順序執行完畢之后,方算作過程執行完畢; 如B為一個異步的調用方法,則在執行完A之后,調用B,並不等待B完成,而是執行開始調用C,待C執行完畢之后,就意味着這個過程執行完畢了。
3、常規的異步調用處理方式
在Java中,一般在處理類似的場景之時,都是基於創建獨立的線程去完成相應的異步調用邏輯,通過主線程和不同的線程之間的執行流程,從而在啟動獨立的線程之后,主線程繼續執行而不會產生停滯等待的情況。
4、@Async介紹
在Spring中,基於@Async標注的方法,稱之為異步方法;這些方法將在執行的時候,將會在獨立的線程中被執行,調用者無需等待它的完成,即可繼續其他的操作。
5、如何在Spring中啟用@Async
基於SpringBoot配置的啟用方式:在 SpringBoot 的啟動類上開啟異步注解支持 @EnableAsync
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args){
SpringApplication.run(DemoApplication.class, args);
}
}
基於XML配置文件的啟用方式,配置如下:
<task:executor id="myexecutor" pool-size="5" /> <task:annotation-driven executor="myexecutor"/>
6、基於@Async無返回值調用 (多用於此方法)
@Async //標注使用 public void asyncMethodWithVoidReturnType() { System.out.println("開啟一個新線程,線程名字叫: " + Thread.currentThread().getName()); }
7、基於@Async 有返回值的調用
二、@Async 的使用注意事項
在實際的項目中,對於一些用時比較長的代碼片段或者函數,我們可以采用異步的方式來執行,這樣就不會影響整體的流程了。比如我在一個用戶請求中需要上傳一些文件,但是上傳文件的耗時會相對來說比較長,這個時候如果上傳文件的成功與否不影響主流程的話,就可以把上傳文件的操作異步化,在spring boot中比較常見的方式就是把要異步執行的代碼片段封裝成一個函數,然后在函數頭使用@Async注解,就可以實現代碼的異步執行。 讓我來復述一下吧!
首先新建一個spring boot項目
package com.example.demo; @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args){ SpringApplication.run(DemoApplication.class, args); } }
新建一個類Task,用來放三個異步任務doTaskOne、doTaskTwo、doTaskThree:
package com.example.demo.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.util.Random; @Component public class Task { public static Random random = new Random(); @Async public void doTaskOne() throws InterruptedException { System.out.println("開始做任務一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務一,耗時:"+ (end - start)); } @Async public void doTaskTwo() throws Exception { System.out.println("開始做任務二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務二,耗時:" + (end - start) + "毫秒"); } @Async public void doTaskThree() throws InterruptedException { System.out.println("開始做任務三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務三,耗時:" + (end - start) + "毫秒"); } }
為了讓這三個方法執行完,我們需要再單元測試用例上的最后一行加上一個延時,不然等函數退出了,異步任務還沒執行完。
在主啟動類上測試,測試這三個方法的執行過程:
package com.example.demo; import com.example.demo.service.UseAsync; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) throws Exception { ApplicationContext context = SpringApplication.run(DemoApplication.class, args); Task task = context.getBean("task", Task.class); task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); Thread.sleep(10000); System.out.println("血魔"); } }
看看我隨意的幾次測試效果:
我們看到三個任務確實是異步執行的,那我們再看看錯誤的使用方法。
在調用這些異步方法的類中寫一個調用這些被@Async注釋方法的方法。
package com.example.demo.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.util.Random; @Component public class Task { public static Random random = new Random(); public void useAsync() throws Exception { doTaskOne(); doTaskTwo(); doTaskThree(); } @Async public void doTaskOne() throws InterruptedException { System.out.println("開始做任務一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務一,耗時:"+ (end - start)); } @Async public void doTaskTwo() throws Exception { System.out.println("開始做任務二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務二,耗時:" + (end - start) + "毫秒"); } @Async public void doTaskThree() throws InterruptedException { System.out.println("開始做任務三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務三,耗時:" + (end - start) + "毫秒"); } }
然后在啟動類調用 這個方法
package com.example.demo; import com.example.demo.service.Task; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) throws Exception { ApplicationContext context = SpringApplication.run(DemoApplication.class, args); Task task = context.getBean("task", Task.class); task.useAsync(); Thread.sleep(5000); System.out.println("血魔"); } }
然后我們來看看這個方法的執行結果:(多實驗幾次)
可以看出這些方法並沒用異步執行,所以 這種在類中調用 本類中被@Async 修飾的方法就不會被異步執行, 所以 @Async 方法盡量放在單獨的類中,而不要擠在 冗余的代碼中。