轉)前 言:
最近在做訂單模塊,用戶購買服務類產品之後,需要進行預約,預約成功之後分別給商家和用戶發送提醒短信。考慮發短信耗時的情況所以我想用異步的方法去執行,於是就在網上看見了Spring的@Async了。但是遇到了許多問題,使得@Async無效,也一直沒有找到很好的文章去詳細的說明@Async的正確及錯誤的使用方法及需要注意的地方,這裏簡單整理了一下遇見的問題,Spring是以配置文件的形式來開啓@Async,而SpringBoot則是以註解的方式開啓。
ps:線程池配合定時任務效果絕佳:https://blog.csdn.net/Muscleheng/article/details/80769884
我們可以使用springBoot默認的線程池,不過一般我們會自定義線程池(因爲比較靈活),配置方式有:
-
使用 xml 文件配置的方式
-
使用Java代碼結合@Configuration進行配置(推薦使用)
下面分別實現兩種配置方式
第一步、配置@Async
一、springBoot啓動類的配置:
在Spring Boot的主程序中配置@EnableAsync,如下所示:
下面展示一些 內聯代碼片
。
@ServletComponentScan
@SpringBootApplication
@EnableAsync
public class ClubApiApplication {
public static void main(String[] args) {
SpringApplication.run(ClubApiApplication.class, args);
}
}
二、Spring XML的配置方式:
- applicationContext.xml同目錄下創建文件threadPool.xml文件:
<!-- 開啓異步,並引入線程池 -->
<task:annotation-driven executor="threadPool" />
<!-- 定義線程池 -->
<bean id="threadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心線程數,默認爲1 -->
<property name="corePoolSize" value="10" />
<!-- 最大線程數,默認爲Integer.MAX_VALUE -->
<property name="maxPoolSize" value="50" />
<!-- 隊列最大長度,一般需要設置值>=notifyScheduledMainExecutor.maxNum;默認爲Integer.MAX_VALUE -->
<property name="queueCapacity" value="100" />
<!-- 線程池維護線程所允許的空閒時間,默認爲60s -->
<property name="keepAliveSeconds" value="30" />
<!-- 完成任務自動關閉 , 默認爲false-->
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<!-- 核心線程超時退出,默認爲false -->
<property name="allowCoreThreadTimeOut" value="true" />
<!-- 線程池對拒絕任務(無線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認爲後者 -->
<property name="rejectedExecutionHandler">
<!-- AbortPolicy:直接拋出java.util.concurrent.RejectedExecutionException異常 -->
<!-- CallerRunsPolicy:主線程直接執行該任務,執行完之後嘗試添加下一個任務到線程池中,可以有效降低向線程池內添加任務的速度 -->
<!-- DiscardOldestPolicy:拋棄舊的任務、暫不支持;會導致被丟棄的任務無法再次被執行 -->
<!-- DiscardPolicy:拋棄當前任務、暫不支持;會導致被丟棄的任務無法再次被執行 -->
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>
2. 然後在applicationContext.xml中引入threadPool.xml:
第二步:創建兩個異步方法的類,如下所示:
第一個類(這裏模擬取消訂單後發短信,有兩個發送短信的方法):
@Service
public class TranTest2Service {
// 發送提醒短信 1
@Async
public void sendMessage1() throws InterruptedException {
System.out.println("發送短信方法---- 1 執行開始");
Thread.sleep(5000); // 模擬耗時
System.out.println("發送短信方法---- 1 執行結束");
}
// 發送提醒短信 2
@Async
public void sendMessage2() throws InterruptedException {
System.out.println("發送短信方法---- 2 執行開始");
Thread.sleep(2000); // 模擬耗時
System.out.println("發送短信方法---- 2 執行結束");
}
}
第二個類。調用發短信的方法 (異步方法不能與被調用的異步方法在同一個類中,否則無效):
@Service
public class OrderTaskServic {
@Autowired
private TranTest2Service tranTest2Service;
// 訂單處理任務
public void orderTask() throws InterruptedException {
this.cancelOrder(); // 取消訂單
tranTest2Service.sendMessage1(); // 發短信的方法 1
tranTest2Service.sendMessage2(); // 發短信的方法 2
}
// 取消訂單
public void cancelOrder() throws InterruptedException {
System.out.println("取消訂單的方法執行------開始");
System.out.println("取消訂單的方法執行------結束 ");
}
}
經過測試得到如下結果:
-
沒有使用@Async
-
使用了@Async
可以看出,沒有使用@Async方式實現的發送短信是同步執行的,意思就是說第一條發送之後再發送第二條,第二條發送成功之後再給用戶提示,這樣顯然會影響用戶體驗,再看使用了@Async實現的,在執行第一個發送短信方法之後馬上開啓另一個線程執行第二個方法,顯然這樣我們的處理速度回快很多。
使用Java代碼結合@Configuration註解的配置方式(推薦使用)
- 新建一個配置類
- 下面展示一些
內聯代碼片
。
package com.boot.common.conf;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* 線程池配置
* @author zhh
*
*/
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
/**
* 默認情況下,在創建了線程池後,線程池中的線程數爲0,當有任務來之後,就會創建一個線程去執行任務,
* 當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;
* 當隊列滿了,就繼續創建線程,當線程數量大於等於maxPoolSize後,開始使用拒絕策略拒絕
*/
/** 核心線程數(默認線程數) */
private static final int corePoolSize = 20;
/** 最大線程數 */
private static final int maxPoolSize = 100;
/** 允許線程空閒時間(單位:默認爲秒) */
private static final int keepAliveTime = 10;
/** 緩衝隊列大小 */
private static final int queueCapacity = 200;
/** 線程池名前綴 */
private static final String threadNamePrefix = "Async-Service-";
@Bean("taskExecutor") // bean的名稱,默認爲首字母小寫的方法名
public ThreadPoolTaskExecutor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveTime);
executor.setThreadNamePrefix(threadNamePrefix);
// 線程池對拒絕任務的處理策略
// CallerRunsPolicy:由調用線程(提交任務的線程)處理該任務
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
}
var foo = 'bar';
2:創建兩個異步方法的類(和之前的類類似僅僅是方法上註解不一樣),如下所示:
第一個類(這裏模擬取消訂單後發短信,有兩個發送短信的方法):
下面展示一些 `內聯代碼片`。
package com.boot.test1.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class TranTest2Service {
Logger log = LoggerFactory.getLogger(TranTest2Service.class);
// 發送提醒短信 1
@PostConstruct // 加上該註解項目啓動時就執行一次該方法
@Async("taskExecutor")
public void sendMessage1() throws InterruptedException {
log.info("發送短信方法---- 1 執行開始");
Thread.sleep(5000); // 模擬耗時
log.info("發送短信方法---- 1 執行結束");
}
// 發送提醒短信 2
@PostConstruct // 加上該註解項目啓動時就執行一次該方法
@Async("taskExecutor")
public void sendMessage2() throws InterruptedException {
log.info("發送短信方法---- 2 執行開始");
Thread.sleep(2000); // 模擬耗時
log.info("發送短信方法---- 2 執行結束");
}
}
代碼中的 @Async(“taskExecutor”) 對應我們自定義線程池中的 @Bean(“taskExecutor”) ,表示使用我們自定義的線程池。
第二個類。調用發短信的方法 (異步方法不能與被調用的異步方法在同一個類中,否則無效):
下面展示一些 內聯代碼片
。
@Service
public class OrderTaskServic {
@Autowired
private TranTest2Service tranTest2Service;
// 訂單處理任務
public void orderTask() throws InterruptedException {
this.cancelOrder(); // 取消訂單
tranTest2Service.sendMessage1(); // 發短信的方法 1
tranTest2Service.sendMessage2(); // 發短信的方法 2
}
// 取消訂單
public void cancelOrder() throws InterruptedException {
System.out.println("取消訂單的方法執行------開始");
System.out.println("取消訂單的方法執行------結束 ");
}
}
運行截圖:
注意看,截圖中的 [nio-8090-exec-1] 是Tomcat的線程名稱
[Async-Service-1]、[Async-Service-2]表示線程1和線程2 ,是我們自定義的線程池裏面的線程名稱,我們在配置類裏面定義的線程池前綴:
private static final String threadNamePrefix = “Async-Service-”; // 線程池名前綴,說明我們自定義的線程池被使用了。
注意事項:
如下方式會使@Async失效
一、異步方法使用static修飾
二、異步類沒有使用@Component註解(或其他註解)導致spring無法掃描到異步類
三、異步方法不能與被調用的異步方法在同一個類中
四、類中需要使用@Autowired或@Resource等註解自動注入,不能自己手動new對象
五、如果使用SpringBoot框架必須在啓動類中增加@EnableAsync註解
原文鏈接:https://blog.csdn.net/Muscleheng/article/details/81409672