日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯網營銷解決方案
聊聊Spring異步任務教程

 阿粉最近碰到一個場景,用戶注冊之后需要發(fā)送郵件給其郵箱。原先設計中,這是一個同步過程,注冊方法需要等待郵件發(fā)送成功才能返回。

站在用戶的角度思考問題,與客戶深入溝通,找到龍圩網站設計與龍圩網站推廣的解決方案,憑借多年的經驗,讓設計與互聯網技術結合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網站設計制作、網站建設、企業(yè)官網、英文網站、手機端網站、網站推廣、申請域名、網絡空間、企業(yè)郵箱。業(yè)務覆蓋龍圩地區(qū)。

由于郵件發(fā)送流程對于注冊來說并不是一個關鍵節(jié)點,我們可以將郵件發(fā)送異步執(zhí)行,減少注冊方法執(zhí)行時間。

我們可以自己創(chuàng)建線程池,然后執(zhí)行異步任務,示例代碼如下:

 
 
 
 
  1. // 生產使用線程池的最佳實踐,一定要自定義線程池,不要嫌麻煩,使用 Executors 創(chuàng)建線程池 
  2. private ThreadPoolExecutor threadPool = 
  3.         new ThreadPoolExecutor(5, 
  4.                 10, 
  5.                 60l, 
  6.                 TimeUnit.SECONDS, 
  7.                 new LinkedBlockingDeque<>(200), 
  8.                 new ThreadFactoryBuilder().setNameFormat("register-%d").build()); 
  9.  
  10. /** 
  11.  * 使用線程池執(zhí)行發(fā)送郵件的任務 
  12.  */ 
  13. private void sendEmailByThreadPool() { 
  14.     threadPool.submit(() -> emailService.sendEmail()); 

ps: 生產使用線程池的最佳實踐,一定要自定義線程池,根據業(yè)務場景設置合理的線程池參數,另外給線程設置具有明確意義的前綴,這樣排查問題就非常簡單。

千萬不要為了方便,使用 Executors 相關方法創(chuàng)建線程池。

上面代碼中使用線程池完成了發(fā)送郵件的異步任務,可以看到這個示例還是有點麻煩,我們不僅要自定義線程池,還需要在創(chuàng)建相關任務執(zhí)行類。

Spring 提供執(zhí)行異步任務功能,我們使用一個注解就可以輕松完成上面的功能。

今天阿粉就來講解一下如何使用 Spring 異步任務,以及 Spring 異步任務使用過程中一些注意點。

異步任務使用方式

Spring 異步任務需要在相關的方法上設置 @Async 注解,這里為了舉例,我們創(chuàng)建一個 EmailService 類,專用完成郵件服務。

代碼如下所示:

 
 
 
 
  1. @Slf4j 
  2. @Service 
  3. public class EmailService { 
  4.  
  5.     /** 
  6.      * 異步發(fā)送任務 
  7.      * 
  8.      * @throws InterruptedException 
  9.      */ 
  10.     @SneakyThrows 
  11.     @Async 
  12.     public void sendEmailAsync() { 
  13.         log.info("使用 Spring 異步任務發(fā)送郵件示例"); 
  14.         // 模擬郵件發(fā)送耗時 
  15.         TimeUnit.SECONDS.sleep(2l); 
  16.     } 

這里要注意了,Spring 異步任務默認關閉的,我們需要使用 @EnableAsync開啟異步任務。

如果還在使用 Spring XML 配置,我們需要配置如下配置:

 
 
 
 
  1.  

上述配置完成之后,我們只需要在調用方,比如上一層 Controller 注入這個 EmailService ,然后直接調用這個方法,該方法將會在異步線程中執(zhí)行。

 
 
 
 
  1. @Slf4j 
  2. @RestController 
  3. public class RegisterController { 
  4.  
  5.     @Autowired 
  6.     EmailService emailService; 
  7.  
  8.     @RequestMapping("register") 
  9.     public String register() { 
  10.      log.info("注冊流程開始"); 
  11.      emailService.sendEmailAsync(); 
  12.         return "success"; 
  13.     } 
  14.  } 

輸出日志如下:

從日志上可以看到,兩個方法執(zhí)行線程不一樣,這就說明了EmailService#sendEmailAsync 被異步線程成功執(zhí)行。

帶有返回值的異步任務

上面的異步任務比較簡單,但是有時我們有需要獲取異步任務返回值。

如果使用線程池執(zhí)行異步任務,我們可以使用 threadPool#submit 獲取返回對象 Future,接著我們就可以調用其內 get 方法,獲取返回結果。

在 Spring 異步任務中,我們也可以使用 Future 獲取返回結果,示例代碼如下:

 
 
 
 
  1. @Async 
  2. @SneakyThrows 
  3. public Future sendEmailAsyncWithResult() { 
  4.     log.info("使用 Spring 異步任務發(fā)送郵件,并且獲取任務返回結果示例"); 
  5.     TimeUnit.SECONDS.sleep(2l); 
  6.     return AsyncResult.forValue("success"); 

這里需要注意,這里返回對象我們需要使用 Spring 內部類 AsyncResult。

Controller 層調用代碼如下所示:

 
 
 
 
  1.  private void sendEmailWithResult() { 
  2.         Future future = emailService.sendEmailAsyncWithResult(); 
  3.         try { 
  4.             String result = future.get(); 
  5.         } catch (InterruptedException e) { 
  6.             e.printStackTrace(); 
  7.         } catch (ExecutionException e) { 
  8.             e.printStackTrace(); 
  9.         } 
  10.  
  11.     } 

我們知道 Future#get 方法將會一直阻塞,直到異步任務執(zhí)行成功。

有時候我們獲取異步任務的返回值是為了做一下后續(xù)業(yè)務,但是主流程方法是無需返回異步任務的返回值。如果我們使用了 Future#get方法,主流程就會一直被阻塞。

對于這種場景,我們可以使用 org.springframework.util.concurrent.ListenableFuture稍微改造一下上面的方法。

ListenableFuture 這個類允許我們注冊回調函數,一旦異步任務執(zhí)行成功,或者執(zhí)行異常,將會立刻執(zhí)行回調函數。通過這種方式就可以不用阻塞執(zhí)行的主線程。

示例代碼如下:

 
 
 
 
  1. @Async 
  2. @SneakyThrows 
  3. public ListenableFuture sendEmailAsyncWithListenableFuture() { 
  4.     log.info("使用 Spring 異步任務發(fā)送郵件,并且獲取任務返回結果示例"); 
  5.     TimeUnit.SECONDS.sleep(2l); 
  6.     return AsyncResult.forValue("success"); 

Controller 層代碼如下所示:

 
 
 
 
  1. ListenableFuture listenableFuture = emailService.sendEmailAsyncWithListenableFuture(); 
  2. // 異步回調處理 
  3. listenableFuture.addCallback(new SuccessCallback() { 
  4.     @Override 
  5.     public void onSuccess(String result) { 
  6.         log.info("異步回調處理返回值"); 
  7.  
  8.     } 
  9. }, new FailureCallback() { 
  10.     @Override 
  11.     public void onFailure(Throwable ex) { 
  12.         log.error("異步回調處理異常",ex); 
  13.     } 
  14. }); 

看到這里,如果有同學有疑惑,我們返回對象是 AsyncResult,為什么方法返回類可以是 Future,又可以是 ListenableFuture?

看完這張類繼承關系,大家應該就知道答案了。

異常處理方式

異步任務中異常處理方式,不是很難,我們只要在方法中將整個代碼塊 try...catch 即可。

 
 
 
 
  1. try { 
  2.  // 其他代碼 
  3. } catch (Exception e) { 
  4.     e.printStackTrace(); 

一般來說,我們只需要捕獲 Exception 異常,就可以應對大部分情況

但是極端情況下,比如方法內發(fā)生 OOM,將會拋出 OutOfMemoryError。如果發(fā)生Error 錯誤,以上的捕獲代碼就會失效。

Spring 的異步任務,默認提供幾種異常處理方式,可以統(tǒng)一處理異步任務中的發(fā)生的異常。

帶有返回值的異常處理方式

如果我們使用帶有返回值的異步任務,處理方式就比較簡單了,我們只需要捕獲 Future#get 拋出的異常就好了。

 
 
 
 
  1. Future future = emailService.sendEmailAsyncWithResult(); 
  2. try { 
  3.     String result = future.get(); 
  4. } catch (InterruptedException e) { 
  5.     e.printStackTrace(); 
  6. } catch (ExecutionException e) { 
  7.     e.printStackTrace(); 

如果我們使用 ListenableFuture 注冊回調函數處理,那我們在方法內增加一個 FailureCallback,在這個實現類處理相關異常即可。

 
 
 
 
  1. ListenableFuture listenableFuture = emailService.sendEmailAsyncWithListenableFuture(); 
  2. // 異步回調處理 
  3. listenableFuture.addCallback(new SuccessCallback() { 
  4.     @Override 
  5.     public void onSuccess(String result) { 
  6.         log.info("異步回調處理返回值"); 
  7.  
  8.     } 
  9.     // 異常處理 
  10. }, new FailureCallback() { 
  11.     @Override 
  12.     public void onFailure(Throwable ex) { 
  13.         log.error("異步回調處理異常",ex); 
  14.     } 
  15. }); 

統(tǒng)一異常處理方式

沒有返回值的異步任務處理方式就比較復雜了,我們需要繼承 AsyncConfigurerSupport,實現 getAsyncUncaughtExceptionHandler 方法,示例代碼如下:

 
 
 
 
  1. @Slf4j 
  2. @Configuration 
  3. public class AsyncErrorHandler extends AsyncConfigurerSupport { 
  4.  
  5.     @Override 
  6.     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { 
  7.         AsyncUncaughtExceptionHandler handler = (throwable, method, objects) -> { 
  8.             log.error("全局異常捕獲", throwable); 
  9.         }; 
  10.         return handler; 
  11.     } 
  12.  

ps:這個異常處理方式只能處理未帶返回值的異步任務。

異步任務使用注意點

異步線程池設置

Spring 異步任務默認使用 Spring 內部線程池 SimpleAsyncTaskExecutor 。

這個線程池比較坑爹,不會復用線程。也就是說來一個請求,將會新建一個線程。極端情況下,如果調用次數過多,將會創(chuàng)建大量線程。

Java 中的線程是會占用一定的內存空間 ,所以創(chuàng)建大量的線程將會導致 OOM 錯誤。

所以如果需要使用異步任務,我們需要一定要使用自定義線程池替換默認線程池。

XML 配置方式

如果當前使用 Spring XML 配置方式,我們可以使用如下配置設置線程池:

 
 
 
 
  1.  
  2.  

注解方式

如果注解方式配置,配置方式如下:

 
 
 
 
  1. @Configuration 
  2. public class AsyncConfiguration { 
  3.  
  4.  
  5.     @Bean 
  6.     public ThreadPoolTaskExecutor taskExecutor() { 
  7.         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 
  8.         executor.setThreadNamePrefix("task-Executor-"); 
  9.         executor.setMaxPoolSize(10); 
  10.         executor.setCorePoolSize(5); 
  11.         executor.setQueueCapacity(200); 
  12.         // 還有其他參數可以設置 
  13.         return executor; 
  14.     } 

只要我們配置了這個線程池Bean,Spring 的異步任務都將會使用該線程池執(zhí)行。

如果我們應用配置了多個線程池Bean,異步任務需要指定使用某個線程池執(zhí)行,我們只需要在 @Async注解上設置相應 Bean 的名字即可。示例代碼如下:

 
 
 
 
  1. @Async("taskExecutor") 
  2. public void sendEmailAsync() { 
  3.     log.info("使用 Spring 異步任務發(fā)送郵件示例"); 
  4.     TimeUnit.SECONDS.sleep(2l); 

Spring Boot 方式

如果是 SpringBoot 項目,從阿粉的測試情況來看,默認將會創(chuàng)建核心線程數為 8,最大線程數為 Integer.MAX_VALUE,隊列數也為 Integer.MAX_VALUE線程池。

雖然上面的線程池不用擔心創(chuàng)建過多線程的問題,不是還是有可能隊列任務過多,導致 OOM 的問題。所以還是建議使用自定義線程池嗎,或者在配置文件修改默認配置,例如:

 
 
 
 
  1. spring.task.execution.pool.core-size=10 
  2. spring.task.execution.pool.max-size=20 
  3. spring.task.execution.pool.queue-capacity=200 

ps:如果我們使用注解方式自定義了一個線程池,那么 Spring 異步任務都將會使用這個線程池。通過 SpringBoot 配置文件創(chuàng)建的線程池將會失效。

異步方法失效

Spring 異步任務背后原理是使用 AOP ,而使用 Spring AOP 時我們需要注意,切勿在方法內部調用其他使用 AOP 的方法,可能有點拗口,我們來看下代碼:

 
 
 
 
  1. @Async 
  2. @SneakyThrows 
  3. public ListenableFuture sendEmailAsyncWithListenableFuture() { 
  4.     // 這樣調用,sendEmailAsync 不會異步執(zhí)行 
  5.     sendEmailAsync(); 
  6.     log.info("使用 Spring 異步任務發(fā)送郵件,并且獲取任務返回結果示例"); 
  7.     TimeUnit.SECONDS.sleep(2l); 
  8.     return AsyncResult.forValue("success"); 
  9.  
  10. /** 
  11.      * 異步發(fā)送任務 
  12.      * 
  13.      * @throws InterruptedException 
  14.      */ 
  15. @SneakyThrows 
  16. @Async("taskExecutor") 
  17. public void sendEmailAsync() { 
  18.     log.info("使用 Spring 異步任務發(fā)送郵件示例"); 
  19.     TimeUnit.SECONDS.sleep(2l); 

上面兩個方法都處于同一個類中,這樣調用將會導致 AOP 失效,無法起到 AOP 的效果。

其他類似的 @Transactional,以及自定義的 AOP 注解都會有這個問題,大家使用過程,千萬需要注意這一點。

總結

Spring 異步任務幫我們大大解決簡化開發(fā)了流程,只要使用一個@Async就可以輕松解決異步任務。

不過,雖然使用方式比較簡單,大家使用過程一定要注意設置合理的線程池。


新聞標題:聊聊Spring異步任務教程
網頁網址:http://m.5511xx.com/article/djdesjj.html