日常開發中,我們偶爾會遇到在業務層中需要同時修改多張表的數據並需要有序的執行,如果用往常的同步的方式,也就是單線程的方式來執行的話,可能會出現執行超時等異常造成請求結果失敗,及時成功,前端也需要等待較長時間來獲取響應結果,這樣不但造成了用戶體驗差,而且會經常出現請求執行失敗的問題,在這里我們一般會采用3種方式來處理。
在采用三種方式之前,我們所有來觀察一下使用同步的方式實現的結果:
1.我們定義一個AsyncController,如下所示:
@Api(value = "異步測試",tags = "異步測試") @RestController public class AsyncController { @Autowired private AsyncService asyncService; /** * 使用傳統方式測試 */ @GetMapping("/test") @ApiOperation(value = "測試異步處理") public void test() { System.out.println("獲取主線程名稱:" + Thread.currentThread().getName()); asyncService.execute(); System.out.println("執行成功,返回結果"); } }
2. 我們定義AsyncService,如下所示:
@Service public class AsyncService { public void execute() { // 這里執行實際的業務邏輯,在這里我們就是用一個簡單的遍歷來模擬 Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).forEach(t -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("獲取number為:" + t); }); } }
執行請求http://localhost:8889/test,結果顯示如下:
獲取主線程名稱:http-nio-8889-exec-4 獲取number為:1 獲取number為:2 獲取number為:3 獲取number為:4 獲取number為:5 獲取number為:6 獲取number為:7 獲取number為:8 獲取number為:9 獲取number為:10 執行成功,返回結果
接下來我們采用我們所說的3種方式來實現:
1.使用線程池的方式來實現
修改AsyncController中的test()方法,service層不變,如下所示:
@Api(value = "異步測試",tags = "異步測試") @RestController public class AsyncController { @Autowired private AsyncService asyncService; /** * 使用傳統方式測試 */ @GetMapping("/test") @ApiOperation(value = "測試異步處理") public void test() { System.out.println("獲取主線程名稱:" + Thread.currentThread().getName()); /** * 定義一個線程池 * 核心線程數(corePoolSize):1 * 最大線程數(maximumPoolSize): 1 * 保持連接時間(keepAliveTime):50000 * 時間單位 (TimeUnit):TimeUnit.MILLISECONDS(毫秒) * 阻塞隊列 new LinkedBlockingQueue<Runnable>() */ ThreadPoolExecutor executor = new ThreadPoolExecutor(1,5,50000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // 執行業務邏輯方法serviceTest() executor.execute(new Runnable() { @Override public void run() { asyncService.execute(); } }); System.out.println("執行成功,返回結果"); } }
執行請求http://localhost:8889/test,結果顯示如下:
獲取主線程名稱:http-nio-8889-exec-1 執行成功,返回結果 獲取number為:1 獲取number為:2 獲取number為:3 獲取number為:4 獲取number為:5 獲取number為:6 獲取number為:7 獲取number為:8 獲取number為:9 獲取number為:10
我們發現在主線程中使用線程池中的線程來實現,程序的執行結果表明,主線程直接將結果進行了返回,然后才是線程池在執行業務邏輯,減少了請求響應時長。
2. 使用注解@EnableAsync和@Async來實現
第一種方式雖然實現了我們想要的結果,但是,我們發現如果我們在多個請求中都需要這種異步請求,每次都要寫這么冗余的線程池配置,所以spring為了提升開發人員的開發效率,使用@EnableAsync來開啟異步的支持,使用@Async來對某個方法進行異步執行。AsyncController如下所示:
@Api(value = "異步測試",tags = "異步測試") @RestController public class AsyncController { @Autowired private AsyncService asyncService; @GetMapping("/test") @ApiOperation(value = "測試異步處理") public void test() { System.out.println("獲取主線程名稱:" + Thread.currentThread().getName()); asyncService.execute(); System.out.println("執行成功,返回結果"); } }
AsyncService如下所示:
@Service @EnableAsync public class AsyncService { /** * 采用異步執行 */ @Async public void execute() { // 這里執行實際的業務邏輯,在這里我們就是用一個簡單的遍歷來模擬 Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).forEach(t -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("獲取number為:" + t); }); } }
執行請求http://localhost:8889/test,結果顯示如下:
獲取主線程名稱:http-nio-8889-exec-1 執行成功,返回結果 獲取number為:1 獲取number為:2 獲取number為:3 獲取number為:4 獲取number為:5 獲取number為:6 獲取number為:7 獲取number為:8 獲取number為:9 獲取number為:10
結果與使用線程池的結果一致,簡化了我們編寫代碼的邏輯,@EnableAsync和@Async注解幫我們實現了避免創建線程池的繁瑣,提高了我們的開發效率,@EnableAsync也可以寫在啟動類上。
3. 使用消息隊列(mq)來實現
當我們涉及的請求在業務邏輯中一次性操作很多很多的數據,例如:一個請求執行業務操作后,將操作日志插入到數據庫中,我們可以使用@Async來實現,但是這樣增加了業務和非業務關系的冗余性,同時如何並發量很大,我們使用@Async處理,一旦服務器宕機,尚未處理的任務會盡數丟失,為了避免這種情況,我們會采用mq來實現,將業務邏輯和非業務邏輯進行隔離執行,互不影響,非業務邏輯不會影響到執行業務邏輯的結果和主機性能,對於mq的處理,我會單獨寫一篇文章來詳細介紹。
文章地址:xxxxx