前言

在實際專案開發中很多業務場景需要使用非同步去完成,比如訊息通知,日誌記錄,等非常常用的都可以通過非同步去執行,提高效率,那麼在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很簡單:

  1. 呼叫非同步方法類上或者啟動類加上註解@EnableAsync
  2. 在需要被非同步呼叫的方法外加上@Async
  3. 所使用的@Async註解方法的類物件應該是Spring容器管理的bean物件;

注意同一個類裡面呼叫非同步方法不生效:原因預設類內的方法呼叫不會被aop攔截,即呼叫方和被呼叫方是在同一個類中,是無法產生切面的,該物件沒有被Spring容器管理。即@Async方法不生效

解決辦法:

如果要使同一個類中的方法之間呼叫也被攔截,需要使用spring容器中的例項物件,而不是使用預設的this,因為通過bean例項的呼叫才會被spring的aop攔截

本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然後使用這個引用呼叫本地的方法即可達到被攔截的目的

備註:這種方法只能攔截protected,default,public方法,private方法無法攔截。這個是spring aop的一個機制。

原理刨析

非同步方法返回型別只能有兩種:

  1. 當返回型別為void的時候,方法呼叫過程產生的異常不會拋到呼叫者層面,可以通過注AsyncUncaughtExceptionHandler來捕獲此類異常

  2. 當返回型別為Future的時候,方法呼叫過程產生的異常會拋到呼叫者層面

注意如果不自定義非同步方法的執行緒池預設使用SimpleAsyncTaskExecutorSimpleAsyncTaskExecutor:不是真的執行緒池,這個類不重用執行緒,每次呼叫都會建立一個新的執行緒。併發大的時候會產生嚴重的效能問題

Spring非同步執行緒池介面 TaskExecutor

看原始碼可知

@FunctionalInterface
public interface TaskExecutor extends Executor {
void execute(Runnable var1);
}

它的實先類有很多如下:

  1. SimpleAsyncTaskExecutor:不是真的執行緒池,這個類不重用執行緒,每次呼叫都會建立一個新的執行緒。

  2. SyncTaskExecutor:這個類沒有實現非同步呼叫,只是一個同步操作。只適用於不需要多執行緒的地方

  3. ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類

  4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的類。執行緒池同時被quartz和非quartz使用,才需要使用此類

  5. ThreadPoolTaskExecutor :最常使用,推薦。 其實質是對java.util.concurrent.ThreadPoolExecutor的包裝,

    ————————————————

文章參考

  1. Spring Boot(5) @Async非同步執行緒池詳解
  2. https://segmentfault.com/a/1190000040134160?utm_source=sf-similar-article