前言
在實際項目開發中很多業務場景需要使用異步去完成,比如消息通知,日志記錄,等非常常用的都可以通過異步去執行,提高效率,那么在Spring
框架中應該如何去使用異步呢
使用步驟
完成異步操作一般有兩種,
消息隊列MQ
,和線程池處理ThreadPoolExecutor
而在Spring4
中提供的對ThreadPoolExecutor封裝的線程池ThreadPoolTaskExecutor
,直接使用注解啟用@Async
,這個注解讓我們在使用Spring完成異步操作變得非常方便
配置線程池類參數配置
自定義常量類
public class ConstantFiledUtil {
public static final String AUTHORIZATION_TOKEN = "authorizationToken";
/**
* kmall線程池名稱
*/
public static final String KMALL_THREAD_POOL = "KmallThreadPool";
/**
* kmall線程名稱前綴
*/
public static final String KMALL_THREAD_NAME_PREFIX = "kmall-thread-";
}
配置線程池
@Configuration(proxyBeanMethods = false)
@EnableAsync //開啟注解
public class KmallConfig {
@Bean(ConstantFiledUtil.KMALL_THREAD_POOL)
public ThreadPoolTaskExecutor FebsShiroThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心線程數
executor.setCorePoolSize(5);
//配置最大線程數
executor.setMaxPoolSize(20);
//配置隊列大小
executor.setQueueCapacity(200);
//線程池維護線程所允許的空閑時間
executor.setKeepAliveSeconds(30);
//配置線程池中的線程的名稱前綴
executor.setThreadNamePrefix(ConstantFiledUtil.KMALL_THREAD_NAME_PREFIX);
//設置線程池關閉的時候等待所有任務都完成再繼續銷毀其他的Bean
executor.setWaitForTasksToCompleteOnShutdown(true);
//設置線程池中任務的等待時間,如果超過這個時候還沒有銷毀就強制銷毀,以確保應用最后能夠被關閉,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
// rejection-policy:當pool已經達到max size的時候,如何處理新任務
// CALLER_RUNS:不在新線程中執行任務,而是由調用者所在的線程來執行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//執行初始化
executor.initialize();
return executor;
}
}
注意這里需要通過
@EnableAsync
開啟異步否則無效
自定義線程任務
public interface ILogService extends IService<Log> {
/**
* 分頁搜索查詢日志信息
* @param logParams
* @return
*/
IPage getSearchLogByPage(SearchLogParams logParams);
/**
* 保存日志
*
* @param logSubject
*/
@Async
void saveLog(LogSubject logSubject);
}
在需要異步執行的接口
,或者方法
上加上@Async
注解此方法就是異步方法,在主程序中調用的化,就是異步方式單獨線程執行
此注解表明saveLog
方法進入的線程池是KmallThreadPool方法創建的。
我們也可以單獨指定方法名稱@Async("saveLogs")
這樣在進行日記記錄的時候就是單獨線程執行每次請求
都快速響應了,而耗時的操作都留給線程池中的線程去異步執行
總結
Spring中用@Async注解標記的方法,稱為異步方法。在spring boot應用中使用@Async很簡單:
- 調用異步方法類上或者啟動類加上注解@EnableAsync
- 在需要被異步調用的方法外加上@Async
- 所使用的@Async注解方法的類對象應該是Spring容器管理的bean對象;
注意同一個類里面調用異步方法不生效:原因默認類內的方法調用不會被
aop
攔截,即調用方和被調用方是在同一個類中,是無法產生切面的,該對象沒有被Spring容器管理。即@Async
方法不生效
解決辦法:
如果要使同一個類中的方法之間調用也被攔截,需要使用spring容器中的實例對象,而不是使用默認的this,因為通過bean實例的調用才會被spring的aop攔截
本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class);
然后使用這個引用調用本地的方法即可達到被攔截的目的
備注:這種方法只能攔截protected,default,public方法,private方法無法攔截。這個是spring aop的一個機制。
原理刨析
異步方法返回類型只能有兩種:
-
當返回類型為void的時候,方法調用過程產生的異常不會拋到調用者層面,可以通過注
AsyncUncaughtExceptionHandler
來捕獲此類異常 -
當返回類型為Future的時候,方法調用過程產生的異常會拋到調用者層面
注意如果不自定義異步方法的線程池默認使用
SimpleAsyncTaskExecutor
。SimpleAsyncTaskExecutor
:不是真的線程池,這個類不重用線程,每次調用都會創建一個新的線程。並發大的時候會產生嚴重的性能問題
。
Spring異步線程池接口 TaskExecutor
看源碼可知
@FunctionalInterface
public interface TaskExecutor extends Executor {
void execute(Runnable var1);
}
它的實先類有很多如下:
-
SimpleAsyncTaskExecutor:
不是真的線程池,這個類不重用線程,每次調用都會創建一個新的線程。 -
SyncTaskExecutor:
這個類沒有實現異步調用,只是一個同步操作。只適用於不需要多線程的地方 -
ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類
-
SimpleThreadPoolTaskExecutor:
是Quartz的SimpleThreadPool的類。線程池同時被quartz和非quartz使用,才需要使用此類 -
ThreadPoolTaskExecutor :
最常使用,推薦。 其實質是對java.util.concurrent.ThreadPoolExecutor的包裝,
————————————————