SpringBoot線程池的創(chuàng)建、@Async配置步驟及注意事項(xiàng)
點(diǎn)擊上方“程序員大白”,選擇“星標(biāo)”公眾號(hào)
重磅干貨,第一時(shí)間送達(dá)
前言
最近在做訂單模塊,用戶購(gòu)買(mǎi)服務(wù)類(lèi)產(chǎn)品之后,需要進(jìn)行預(yù)約,預(yù)約成功之后分別給商家和用戶發(fā)送提醒短信??紤]發(fā)短信耗時(shí)的情況所以我想用異步的方法去執(zhí)行,于是就在網(wǎng)上看見(jiàn)了Spring的@Async了。
但是遇到了許多問(wèn)題,使得@Async無(wú)效,也一直沒(méi)有找到很好的文章去詳細(xì)的說(shuō)明@Async的正確及錯(cuò)誤的使用方法及需要注意的地方,這里簡(jiǎn)單整理了一下遇見(jiàn)的問(wèn)題,Sring是以配置文件的形式來(lái)開(kāi)啟@Async,而SpringBoot則是以注解的方式開(kāi)啟。
我們可以使用springBoot默認(rèn)的線程池,不過(guò)一般我們會(huì)自定義線程池(因?yàn)楸容^靈活),配置方式有:
使用 xml 文件配置的方式
使用Java代碼結(jié)合@Configuration進(jìn)行配置(推薦使用)
下面分別實(shí)現(xiàn)兩種配置方式
第一步、配置@Async
一、springBoot啟動(dòng)類(lèi)的配置:
在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同目錄下創(chuàng)建文件threadPool.xml文件:
??
<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.xsd??
????????????????????????http://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">??
??????????
????????<property?name="corePoolSize"?value="10"?/>??
??
??????????
????????<property?name="maxPoolSize"?value="50"?/>??
??
??????????
????????<property?name="queueCapacity"?value="100"?/>??
??
??????????
????????<property?name="keepAliveSeconds"?value="30"?/>??
??
??????????
????????<property?name="waitForTasksToCompleteOnShutdown"?value="true"?/>??
??
??????????
????????<property?name="allowCoreThreadTimeOut"?value="true"?/>??
??
??????????
????????<property?name="rejectedExecutionHandler">??
??????????????
??????????????
??????????????
??????????????
????????????<bean?class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"?/>??
????????property>??
????bean>??
beans>??
2.然后在applicationContext.xml中引入threadPool.xml:
??
??
<import?resource="threadPool.xml"?/>??
<task:annotation-driven?executor="WhifExecutor"?/>??
第二步:創(chuàng)建兩個(gè)異步方法的類(lèi),如下所示:
第一個(gè)類(lèi)(這里模擬取消訂單后發(fā)短信,有兩個(gè)發(fā)送短信的方法):
@Service??
public?class?TranTest2Service?{??
??
??
????//?發(fā)送提醒短信?1??
????@Async??
????public?void?sendMessage1()?throws?InterruptedException?{??
??
????????System.out.println("發(fā)送短信方法----?1???執(zhí)行開(kāi)始");??
????????Thread.sleep(5000);?//?模擬耗時(shí)??
????????System.out.println("發(fā)送短信方法----?1???執(zhí)行結(jié)束");??
????}??
??
????//?發(fā)送提醒短信?2??
????@Async??
????public?void?sendMessage2()?throws?InterruptedException?{??
??
????????System.out.println("發(fā)送短信方法----?2???執(zhí)行開(kāi)始");??
????????Thread.sleep(2000);?//?模擬耗時(shí)??
????????System.out.println("發(fā)送短信方法----?2???執(zhí)行結(jié)束");??
????}??
}??
第二個(gè)類(lèi)。調(diào)用發(fā)短信的方法 (異步方法不能與被調(diào)用的異步方法在同一個(gè)類(lèi)中,否則無(wú)效):
@Service??
public?class?OrderTaskServic?{??
????@Autowired??
????private?TranTest2Service?tranTest2Service;??
??
????//?訂單處理任務(wù)??
????public?void?orderTask()?throws?InterruptedException?{??
??
????????this.cancelOrder();?//?取消訂單??
????????tranTest2Service.sendMessage1();?//?發(fā)短信的方法???1??
????????tranTest2Service.sendMessage2();?//?發(fā)短信的方法??2??
??
????}??
??
????//?取消訂單??
????public?void?cancelOrder()?throws?InterruptedException?{??
????????System.out.println("取消訂單的方法執(zhí)行------開(kāi)始");??
????????System.out.println("取消訂單的方法執(zhí)行------結(jié)束?");??
????}??
??
}??
經(jīng)過(guò)測(cè)試得到如下結(jié)果:
1.沒(méi)有使用@Async

2.使用了@Async

可以看出,沒(méi)有使用@Async方式實(shí)現(xiàn)的發(fā)送短信是同步執(zhí)行的,意思就是說(shuō)第一條發(fā)送之后再發(fā)送第二條,第二條發(fā)送成功之后再給用戶提示,這樣顯然會(huì)影響用戶體驗(yàn),再看使用了@Async實(shí)現(xiàn)的,在執(zhí)行第一個(gè)發(fā)送短信方法之后馬上開(kāi)啟另一個(gè)線程執(zhí)行第二個(gè)方法,顯然這樣我們的處理速度回快很多。
使用Java代碼結(jié)合@Configuration注解的配置方式(推薦使用)
1. 新建一個(gè)配置類(lèi)
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?{??
??
/**???
?*???默認(rèn)情況下,在創(chuàng)建了線程池后,線程池中的線程數(shù)為0,當(dāng)有任務(wù)來(lái)之后,就會(huì)創(chuàng)建一個(gè)線程去執(zhí)行任務(wù),??
?*????當(dāng)線程池中的線程數(shù)目達(dá)到corePoolSize后,就會(huì)把到達(dá)的任務(wù)放到緩存隊(duì)列當(dāng)中;??
?*??當(dāng)隊(duì)列滿了,就繼續(xù)創(chuàng)建線程,當(dāng)線程數(shù)量大于等于maxPoolSize后,開(kāi)始使用拒絕策略拒絕???
?*/??
??
????/**?核心線程數(shù)(默認(rèn)線程數(shù))?*/??
????private?static?final?int?corePoolSize?=?20;??
????/**?最大線程數(shù)?*/??
????private?static?final?int?maxPoolSize?=?100;??
????/**?允許線程空閑時(shí)間(單位:默認(rèn)為秒)?*/??
????private?static?final?int?keepAliveTime?=?10;??
????/**?緩沖隊(duì)列大小?*/??
????private?static?final?int?queueCapacity?=?200;??
????/**?線程池名前綴?*/??
????private?static?final?String?threadNamePrefix?=?"Async-Service-";??
??
????@Bean("taskExecutor")?//?bean的名稱(chēng),默認(rèn)為首字母小寫(xiě)的方法名??
????public?ThreadPoolTaskExecutor?taskExecutor(){??
????????ThreadPoolTaskExecutor?executor?=?new?ThreadPoolTaskExecutor();??
????????executor.setCorePoolSize(corePoolSize);?????
????????executor.setMaxPoolSize(maxPoolSize);??
????????executor.setQueueCapacity(queueCapacity);??
????????executor.setKeepAliveSeconds(keepAliveTime);??
????????executor.setThreadNamePrefix(threadNamePrefix);??
??
????????//?線程池對(duì)拒絕任務(wù)的處理策略??
????????// CallerRunsPolicy:由調(diào)用線程(提交任務(wù)的線程)處理該任務(wù)??
????????executor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());??
????????//?初始化??
????????executor.initialize();??
????????return?executor;??
????}??
}??
2.創(chuàng)建兩個(gè)異步方法的類(lèi)(和之前的類(lèi)類(lèi)似僅僅是方法上注解不一樣),如下所示:
第一個(gè)類(lèi)(這里模擬取消訂單后發(fā)短信,有兩個(gè)發(fā)送短信的方法):
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);??
??
????//?發(fā)送提醒短信?1??
????????@PostConstruct?//?加上該注解項(xiàng)目啟動(dòng)時(shí)就執(zhí)行一次該方法??
????@Async("taskExecutor")??
????public?void?sendMessage1()?throws?InterruptedException?{??
????????log.info("發(fā)送短信方法----?1???執(zhí)行開(kāi)始");??
????????Thread.sleep(5000);?//?模擬耗時(shí)??
????????log.info("發(fā)送短信方法----?1???執(zhí)行結(jié)束");??
????}??
??
????//?發(fā)送提醒短信?2??
????????@PostConstruct?//?加上該注解項(xiàng)目啟動(dòng)時(shí)就執(zhí)行一次該方法??
????@Async("taskExecutor")??
????public?void?sendMessage2()?throws?InterruptedException?{??
??
????????log.info("發(fā)送短信方法----?2???執(zhí)行開(kāi)始");??
????????Thread.sleep(2000);?//?模擬耗時(shí)??
????????log.info("發(fā)送短信方法----?2???執(zhí)行結(jié)束");??
????}??
}??
代碼中的 @Async("taskExecutor") 對(duì)應(yīng)我們自定義線程池中的 @Bean("taskExecutor") ,表示使用我們自定義的線程池。
第二個(gè)類(lèi)。調(diào)用發(fā)短信的方法 (異步方法不能與被調(diào)用的異步方法在同一個(gè)類(lèi)中,否則無(wú)效):
@Service??
public?class?OrderTaskServic?{??
????@Autowired??
????private?TranTest2Service?tranTest2Service;??
??
????//?訂單處理任務(wù)??
????public?void?orderTask()?throws?InterruptedException?{??
??
????????this.cancelOrder();?//?取消訂單??
????????tranTest2Service.sendMessage1();?//?發(fā)短信的方法???1??
????????tranTest2Service.sendMessage2();?//?發(fā)短信的方法??2??
??
????}??
??
????//?取消訂單??
????public?void?cancelOrder()?throws?InterruptedException?{??
????????System.out.println("取消訂單的方法執(zhí)行------開(kāi)始");??
????????System.out.println("取消訂單的方法執(zhí)行------結(jié)束?");??
????}??
??
}??
運(yùn)行截圖:

注意看,截圖中的 [nio-8090-exec-1] 是Tomcat的線程名稱(chēng)
[Async-Service-1]、[Async-Service-2]表示線程1和線程2 ,是我們自定義的線程池里面的線程名稱(chēng),我們?cè)谂渲妙?lèi)里面定義的線程池前綴:
private static final String threadNamePrefix = "Async-Service-"; // 線程池名前綴,說(shuō)明我們自定義的線程池被使用了。
注意事項(xiàng)
如下方式會(huì)使@Async失效
異步方法使用static修飾
異步類(lèi)沒(méi)有使用@Component注解(或其他注解)導(dǎo)致spring無(wú)法掃描到異步類(lèi)
異步方法不能與被調(diào)用的異步方法在同一個(gè)類(lèi)中
類(lèi)中需要使用@Autowired或@Resource等注解自動(dòng)注入,不能自己手動(dòng)new對(duì)象
如果使用SpringBoot框架必須在啟動(dòng)類(lèi)中增加@EnableAsync注解
來(lái)源:blog.csdn.net/Muscleheng/article/details/81409672
推薦閱讀
關(guān)于程序員大白
程序員大白是一群哈工大,東北大學(xué),西湖大學(xué)和上海交通大學(xué)的碩士博士運(yùn)營(yíng)維護(hù)的號(hào),大家樂(lè)于分享高質(zhì)量文章,喜歡總結(jié)知識(shí),歡迎關(guān)注[程序員大白],大家一起學(xué)習(xí)進(jìn)步!


