1.功能說明
Spring提供了Async注解來實現方法的異步調用。
即當調用Async標識的方法時,調用線程不會等待被調用方法執行完成即返回繼續執行以下操作,而被調用的方法則會啟動一個獨立線程來執行此方法。
這種異步執行的方式通常用於處理接口中不需要返回給用戶的數據處理。比如當注冊的時候,只需要將用戶信息返回用戶,而關於信息的保存操作可以使用異步執行。
Spring提供了Scheduled注解來實現定時任務的功能。
在異步方法和定時任務功能中都是開發這自己定義需要執行的方法,然后交給Spring容器管理線程,並執行相應的方法。在使用異步方法和定時任務的時候需要特別注意的是線程池的配置以及任務中異常的處理。下面對這兩個功能進行簡單介紹。
2.關鍵注解和配置接口
功能開啟注解:
EnableAsync和EnableScheduling
通過在Spring的配置類中添加這兩個注解來開啟Spring的異步方法和定時任務的功能。
異步方法標識注解Async,其定義為:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
String value() default "";
}
在注解定義中可以看到此注解可以用於type和method,當此注解用於類的時候,表示此類中的所有方法都為異步方法。此注解中的value屬性可用於指定執行此異步方法的線程池。線程池的具體確定方法下面具體分析。
定時任務標識注解Scheduled,定義如下:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
定時任務
String cron() default "";
String zone() default "";
//上次執行結束到下次執行開始
long fixedDelay() default -1;
String fixedDelayString() default "";
上次執行開始到本次執行開始
long fixedRate() default -1;
fixedRate或者fixedDelay的時候第一次的延遲時間
long initialDelay() default -1;
}
3.Spring線程池的選擇和自定義配置線程池
在項目中我們通常不會自己手動創建線程,而是通過統一的線程池來執行task或者異步方法,使用這種方法來避免多人團隊中由於自定義線程導致的資源耗盡的問題。在自定義線程池之前首先要了解Spring在執行異步任務或者方法的時候是怎么選擇線程池的。
3.1 Async對於線程池的選擇順序
Async線程池的選擇順序如下圖所示:
Spring在執行async標識的異步方法的時候首先會在Spring的上下文中搜索類型為TaskExecutor或者名稱為“taskExecutor”的bean,當可以找到的時候,就將任務提交到此線程池中執行。當不存在以上線程池的時候,Spring會手動創建一個SimpleAsyncTaskExecutor執行異步任務。
另外當標識async注解的時候指定了,value值,Spring會使用指定的線程池執行。比如以下:
@Async(value = "asyncTaskThreadPool")
這個時候Spring會去上下文中找名字為asyncTaskThreadPool的bean,並執行異步任務,找不到,會拋出異常。
3.2 Scheduled對於線程池的選擇順序
Scheduled對於線程池的選擇順序如下圖所示:
當Spring執行定時任務的時候,首先會在上下文中找類型為TaskScheduler或者名稱為taskScheduler的bean,找不到的時候會手動創建一個線程執行此task。
3.3 自定義線程池和異常處理
在了解了Spring對於線程池的選擇后,我們需要自定義線程池。自定義Async線程池有三種方式。
方法一:首先配置接口,重寫獲取線程池的方法。
配置Async方法的線程池需要繼承AsyncConfigurerSupport類,或者實現AsyncConfigurer接口,並重寫getAsyncExecutor方法,代碼如下:
@Configuration
@EnableAsync
public class ThreadPoolBeanFactory extends AsyncConfigurerSupport{
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor asyncTaskThreadPool = new ThreadPoolTaskExecutor();
asyncTaskThreadPool.setCorePoolSize(100);
asyncTaskThreadPool.setMaxPoolSize(200);
asyncTaskThreadPool.setQueueCapacity(11);
asyncTaskThreadPool.setThreadFactory(new ThreadFactory() {
private final AtomicLong index = new AtomicLong(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Async-override-task-pool-thread-" + index.getAndIncrement());
}
});
asyncTaskThreadPool.initialize();
return asyncTaskThreadPool;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
//返回值為void的異步方法不會傳遞異常,當方法中出現異常的時候只會打印日志,重寫此方法來自定義異常處理方法
return null;
}
}
這種定義的方法缺點是沒有定義bean。
方法二:自定義相應類型的線程池bean。
第二種方法是基於Spring對線程選擇的原理來實現的,定義一個類型為TaskExecutor的bean,定義方式如下:
@Bean
public TaskExecutor asyncTaskThreadPool() {
ThreadPoolTaskExecutor asyncTaskThreadPool = new ThreadPoolTaskExecutor();
asyncTaskThreadPool.setCorePoolSize(100);
asyncTaskThreadPool.setMaxPoolSize(200);
asyncTaskThreadPool.setThreadFactory(new ThreadFactory() {
private final AtomicLong index = new AtomicLong(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Async-task-pool-thread-" + index.getAndIncrement());
}
});
// asyncTaskThreadPool.initialize();//當為bean的時候不需要調用此方法,裝載容器的時候回自動調用
return asyncTaskThreadPool;
}
以上兩種方式定義線程池的時候在定義異步方法可以不執行線程池。定義如下:
@Async
public void test(){
System.out.println(Thread.currentThread().getName());
}
此時Spring會自動使用以上定義的線程池執行此方法。使用以上兩種配置輸出結果依次是:
Async-task-pool-thread-1
Async-task-override-pool-thread-1
方法三 在Async注解中執行線程池名稱
異步任務定義如下:
@Async(value = "asyncTaskThreadPool")
public void asyncTask2() {
LOGGER.info("AsyncTask2 start.");
LOGGER.info(Thread.currentThread().getName());
LOGGER.info("AsyncTask2 finished.");
}
此時Spring會在上下文中找名稱為asyncTaskThreadPool的線程池來執行此任務。
類似的可以自定義Scheduled的線程池,需要實現的配置接口為:SchedulingConfigurer。方法類似。
4.Async返回操作結果
異步任務可以通過定義返回類型為Future來實現返回值,定義如下:
@Async
public Future<String> asyncTaskWithResult() {
LOGGER.info("AsyncTaskWithResult start.");
try {
Thread.sleep(1000 * 10);
} catch (Exception e) {
return new AsyncResult<>("error" + e.getMessage());
}
LOGGER.info("AsyncTaskWithResult finished.");
return new AsyncResult<>("success");
}
5.編寫單元測試測試功能
單元測試代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncApplicationTests {
@Autowired
private AsyncTaskService asyncTaskService;
@Test
public void asyncTest() throws Exception{
Future<String> future = asyncTaskService.asyncTaskWithResult();
while (!future.isDone()) {
System.out.println("Wait asyncTaskWithResult.");
Thread.sleep(1000);
}
System.out.println("asyncTaskWithResult result is:" + future.get());
System.out.println("asyncTask finished.");
}
}
輸出內容如下:
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
AsyncTaskWithResult finished.
asyncTaskWithResult result is:success
asyncTask finished.