1. 程式人生 > 實用技巧 >Springboot中的@EnableAsync和@Async

Springboot中的@EnableAsync和@Async

在我們的日常開發中,我們偶爾會遇到在業務層中我們需要同時修改多張表的資料並且需要有序的執行,如果我們用往常的同步的方式,也就是單執行緒的方式來執行的話,可能會出現執行超時等異常造成請求結果失敗,及時成功,前端也需要等待較長時間來獲取響應結果,這樣不但造成了使用者體驗差,而且會經常出現請求執行失敗的問題,在這裡我們一般會採用3種方式來處理,如下所示:

在採用三種方式之前,我們所有來觀察一下使用同步的方式實現的結果:

1.我們定義一個TestController,如下所示:


  1. @RestController

  2. public class TestController {

  3. @Autowired

  4. private TestService service;

  5. /**

  6. * 使用傳統方式測試

  7. */

  8. @GetMapping("/test")

  9. public void test() {

  10. System.out.println("獲取主執行緒名稱:" + Thread.currentThread().getName());

  11. service.serviceTest();

  12. System.out.println("執行成功,返回結果");

  13. }

  14. }

2. 我們定義TestService,如下所示:


  1. @Service

  2. public class TestAsyncService {

  3. public void serviceTest() {

  4. // 這裡執行實際的業務邏輯,在這裡我們就是用一個簡單的遍歷來模擬

  5. Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10}).forEach( t -> {

  6. try {

  7. Thread.sleep(100);

  8. } catch (InterruptedException e) {

  9. e.printStackTrace();

  10. }

  11. System.out.println("獲取number為:" + t) ;

  12. });

  13. }

  14. }

我們執行請求http://localhost:8888/test,結果顯示如下:

接下來我們採用我們所說的3種方式來實現:

1.使用執行緒池的方式來實現

.(1)定義TestAsyncController中的test()方法,如下所示:


  1. @RestController

  2. public class TestAsyncController {

  3. @Autowired

  4. private TestAsyncService asyncService;

  5. /**

  6. * 使用傳統方式測試

  7. */

  8. @GetMapping("/test")

  9. public void test() {

  10. System.out.println("獲取主執行緒名稱:" + Thread.currentThread().getName());

  11. /**

  12. * 這裡也可以採用以下方式使用,但是使用執行緒池的方式可以很便捷的對執行緒管理(提高程式的整體效能),

  13. * 也可以減少每次執行該請求時都需要建立一個執行緒造成的效能消耗

  14. * new Thread(() ->{

  15. * run方法中的業務邏輯

  16. * })

  17. */

  18. /**

  19. * 定義一個執行緒池

  20. * 核心執行緒數(corePoolSize):1

  21. * 最大執行緒數(maximumPoolSize): 1

  22. * 保持連線時間(keepAliveTime):50000

  23. * 時間單位 (TimeUnit):TimeUnit.MILLISECONDS(毫秒)

  24. * 阻塞佇列 new LinkedBlockingQueue<Runnable>()

  25. */

  26. ThreadPoolExecutor executor = new ThreadPoolExecutor(1,5,50000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

  27. // 執行業務邏輯方法serviceTest()

  28. executor.execute(new Runnable() {

  29. @Override

  30. public void run() {

  31. asyncService.serviceTest();

  32. }

  33. });

  34. System.out.println("執行完成,向用戶響應成功資訊");

  35. }

  36. }

(2).定義TestAsyncService和TestService中的test()相同,在這就不顯示了;

(3).檢視執行結果:

我們發現在主執行緒中使用執行緒池中的執行緒來實現,程式的執行結果表明,主執行緒直接將結果進行了返回,然後才是執行緒池在執行業務邏輯,減少了請求響應時長。

2. 使用註解@EnableAsync和@Async來實現

(1)。雖然這樣實現了我們想要的結果,但是,但是我們發現如果我們在多個請求中都需要這種非同步請求,每次都寫這麼冗餘的執行緒池配置會不會,這種問題當然會被我們強大的spring所觀察到,所以spring為了提升開發人員的開發效率,使用@EnableAsync來開啟非同步的支援,使用@Async來對某個方法進行非同步執行。TestAsyncController如下所示:


  1. @RestController

  2. public class TestAsyncController {

  3. @Autowired

  4. private TestAsyncService asyncService;

  5. /**

  6. * 使用@Async來實現

  7. */

  8. @GetMapping("/test")

  9. public void test() {

  10. System.out.println("獲取主執行緒名稱:" + Thread.currentThread().getName());

  11. // 非同步呼叫

  12. asyncService.serviceTest();

  13. System.out.println("執行完成,向用戶響應成功資訊");

  14. }

(2)TestAsyncService如下所示:


  1. @Service

  2. @EnableAsync

  3. public class TestAsyncService {

  4. /**

  5. * 採用非同步執行

  6. */

  7. @Async

  8. public void serviceTest() {

  9. // 這裡執行實際的業務邏輯,在這裡我們就是用一個簡單的遍歷來模擬

  10. Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10}).forEach( t -> {

  11. try {

  12. Thread.sleep(100);

  13. } catch (InterruptedException e) {

  14. e.printStackTrace();

  15. }

  16. System.out.println("獲取number為:" + t) ;

  17. });

  18. }

(3)執行結果如下:

結果與使用執行緒池的結果一致,但是簡化了我們編寫程式碼的邏輯,@Async註解幫我們實現了建立執行緒池的繁瑣,提高了我們的開發效率。

3. 使用訊息佇列(mq)來實現

當我們涉及的請求在業務邏輯中一次性操作很多很多的資料,例如:一個請求執行相關業務操作後,將操作日誌插入到資料庫中,我們可以使用@Async來實現,但是這樣增加了業務和非業務關係的冗餘性(同時如何併發量很大,我們使用@Async處理,無法提升我們系統的整體系統,這樣很容易造成伺服器宕機),所以我們對於這種情況,我們會採用mq來實現,將業務邏輯和非業務邏輯進行隔離執行,互不影響,非業務邏輯不會影響到執行業務邏輯的結果和主機效能