javascript
Spring Boot 线程池的创建、@Async 配置步骤及注意事项
??點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達 今日推薦:終于放棄了單調的swagger-ui了,選擇了這款神器—knife4j個人原創+1博客:點擊前往,查看更多作者?| Muscleheng
blog.csdn.net/Muscleheng/article/details/81409672
前言
最近在做訂單模塊,用戶購買服務類產品之后,需要進行預約,預約成功之后分別給商家和用戶發送提醒短信。考慮發短信耗時的情況所以我想用異步的方法去執行,于是就在網上看見了Spring的@Async了。
但是遇到了許多問題,使得@Async無效,也一直沒有找到很好的文章去詳細的說明@Async的正確及錯誤的使用方法及需要注意的地方,這里簡單整理了一下遇見的問題,Sring是以配置文件的形式來開啟@Async,而SpringBoot則是以注解的方式開啟。
我們可以使用springBoot默認的線程池,不過一般我們會自定義線程池(因為比較靈活),配置方式有:
使用 xml 文件配置的方式
使用Java代碼結合@Configuration進行配置(推薦使用)
下面分別實現兩種配置方式
附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,這是本人這幾年及春招的總結,目前,已經拿到了大廠offer,拿去不謝!
下載方式
1.?首先掃描下方二維碼
2.?后臺回復「Java面試」即可獲取
第一步、配置@Async
一、Spring Boot啟動類的配置:
在Spring Boot的主程序中配置@EnableAsync,如下所示:
@ServletComponentScan @SpringBootApplication @EnableAsync public class ClubApiApplication {public?static?void?main(String[] args) {SpringApplication.run(ClubApiApplication.class, args);} }二、Spring XML的配置方式:
1.applicationContext.xml同目錄下創建文件threadPool.xml文件:
<?xml version="1.0"?encoding="UTF-8"?> <beans?xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:task="http://www.springframework.org/schema/task"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"><!-- 開啟異步,并引入線程池 --><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> </beans>2.然后在applicationContext.xml中引入threadPool.xml:<import resource="threadPool.xml" />
<!--如果不使用自定義線程池,可以直接使用下面這段標簽--> <!-- <task:executor id="WhifExecutor" pool-size="10"/> --> <import?resource="threadPool.xml"?/> <task:annotation-driven?executor="WhifExecutor"?/>第二步:創建兩個異步方法的類,如下所示:
第一個類(這里模擬取消訂單后發短信,有兩個發送短信的方法):
@Service public?class?TranTest2Service?{// 發送提醒短信 1@Asyncpublic?void?sendMessage1()?throws?InterruptedException {System.out.println("發送短信方法---- 1 執行開始");Thread.sleep(5000); // 模擬耗時System.out.println("發送短信方法---- 1 執行結束");}// 發送提醒短信 2@Asyncpublic?void?sendMessage2()?throws?InterruptedException {System.out.println("發送短信方法---- 2 執行開始");Thread.sleep(2000); // 模擬耗時System.out.println("發送短信方法---- 2 執行結束");} }第二個類。調用發短信的方法 (異步方法不能與被調用的異步方法在同一個類中,否則無效):
@Service public?class?OrderTaskServic?{@Autowiredprivate?TranTest2Service tranTest2Service;// 訂單處理任務public?void?orderTask()?throws?InterruptedException {this.cancelOrder(); // 取消訂單tranTest2Service.sendMessage1(); // 發短信的方法 1tranTest2Service.sendMessage2(); // 發短信的方法 2}// 取消訂單public?void?cancelOrder()?throws?InterruptedException {System.out.println("取消訂單的方法執行------開始");System.out.println("取消訂單的方法執行------結束 ");}}經過測試得到如下結果:
1.沒有使用@Async
2.使用了@Async
可以看出,沒有使用@Async方式實現的發送短信是同步執行的,意思就是說第一條發送之后再發送第二條,第二條發送成功之后再給用戶提示,這樣顯然會影響用戶體驗,再看使用了@Async實現的,在執行第一個發送短信方法之后馬上開啟另一個線程執行第二個方法,顯然這樣我們的處理速度回快很多。
使用Java代碼結合@Configuration注解的配置方式(推薦使用)
1. 新建一個配置類
/*** 線程池配置* @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;} }2.創建兩個異步方法的類(和之前的類類似僅僅是方法上注解不一樣),如下所示:
第一個類(這里模擬取消訂單后發短信,有兩個發送短信的方法):
@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?{@Autowiredprivate?TranTest2Service tranTest2Service;// 訂單處理任務public?void?orderTask()?throws?InterruptedException {this.cancelOrder(); // 取消訂單tranTest2Service.sendMessage1(); // 發短信的方法 1tranTest2Service.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注解
?
最后,再附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,這是本人這幾年及春招的總結,目前,已經拿到了大廠offer,拿去不謝!
下載方式
1.?首先掃描下方二維碼
2.?后臺回復「Java面試」即可獲取
總結
以上是生活随笔為你收集整理的Spring Boot 线程池的创建、@Async 配置步骤及注意事项的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10分钟白嫖我常用的20个在线工具类网站
- 下一篇: 终于当了回up主,来白嫖我历时半年总结的