Spring Boot之@Async異步線程池示例詳解
前言
很多業(yè)務(wù)場(chǎng)景需要使用異步去完成,比如:發(fā)送短信通知。要完成異步操作一般有兩種:
- 1、消息隊(duì)列MQ
- 2、線程池處理。
我們來(lái)看看Spring框架中如何去使用線程池來(lái)完成異步操作,以及分析背后的原理。
一. Spring異步線程池的接口類 :TaskExecutor
在Spring4中,Spring中引入了一個(gè)新的注解@Async,這個(gè)注解讓我們?cè)谑褂肧pring完成異步操作變得非常方便。
Spring異步線程池的接口類,其實(shí)質(zhì)是java.util.concurrent.Executor
Spring 已經(jīng)實(shí)現(xiàn)的異常線程池:
1. SimpleAsyncTaskExecutor:不是真的線程池,這個(gè)類不重用線程,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程。
2. SyncTaskExecutor:這個(gè)類沒(méi)有實(shí)現(xiàn)異步調(diào)用,只是一個(gè)同步操作。只適用于不需要多線程的地方
3. ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時(shí),才用考慮使用這個(gè)類
4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的類。線程池同時(shí)被quartz和非quartz使用,才需要使用此類
5. ThreadPoolTaskExecutor :最常使用,推薦。 其實(shí)質(zhì)是對(duì)java.util.concurrent.ThreadPoolExecutor的包裝,
關(guān)于java-多線程和線程池:http://m.fzitv.net/article/222986.htm
我們查看ThreadPoolExecutor初始化的源碼就知道使用ThreadPoolExecutor。

二、簡(jiǎn)單使用說(shuō)明
Spring中用@Async注解標(biāo)記的方法,稱為異步方法。在spring boot應(yīng)用中使用@Async很簡(jiǎn)單:
1、調(diào)用異步方法類上或者啟動(dòng)類加上注解@EnableAsync
2、在需要被異步調(diào)用的方法外加上@Async
3、所使用的@Async注解方法的類對(duì)象應(yīng)該是Spring容器管理的bean對(duì)象;
啟動(dòng)類加上注解@EnableAsync:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class CollectorApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(CollectorApplication.class, args);
}
}
在需要被異步調(diào)用的方法外加上@Async,同時(shí)類AsyncService加上注解@Service或者@Component,使其對(duì)象成為Spring容器管理的bean對(duì)象;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class AsyncService {
@Async
public void asyncMethod(String s) {
System.out.println("receive:" + s);
}
public void test() {
System.out.println("test");
asyncMethod();//同一個(gè)類里面調(diào)用異步方法
}
@Async
public void test2() {
AsyncService asyncService = context.getBean(AsyncService.class);
asyncService.asyncMethod();//異步
}
/**
* 異布調(diào)用返回Future
*/
@Async
public Future<String> asyncInvokeReturnFuture(int i) {
System.out.println("asyncInvokeReturnFuture, parementer="+ i);
Future<String> future;
try {
Thread.sleep(1000 * 1);
future = new AsyncResult<String>("success:" + i);
} catch (InterruptedException e) {
future = new AsyncResult<String>("error");
}
return future;
}
}
//異步方法和普通的方法調(diào)用相同
asyncService.asyncMethod("123");
Future<String> future = asyncService.asyncInvokeReturnFuture(100);
System.out.println(future.get());
如果將一個(gè)類聲明為異步類@Async,那么這個(gè)類對(duì)外暴露的方法全部成為異步方法。
@Async
@Service
public class AsyncClass {
public AsyncClass() {
System.out.println("----init AsyncClass----");
}
volatile int index = 0;
public void foo() {
System.out.println("asyncclass foo, index:" + index);
}
public void foo(int i) {
this.index = i;
System.out.println("asyncclass foo, index:" + i);
}
public void bar(int i) {
this.index = i;
System.out.println("asyncclass bar, index:" + i);
}
}
這里需要注意的是:
1、同一個(gè)類里面調(diào)用異步方法不生效:原因默認(rèn)類內(nèi)的方法調(diào)用不會(huì)被aop攔截,即調(diào)用方和被調(diào)用方是在同一個(gè)類中,是無(wú)法產(chǎn)生切面的,該對(duì)象沒(méi)有被Spring容器管理。即@Async方法不生效。
解決辦法:如果要使同一個(gè)類中的方法之間調(diào)用也被攔截,需要使用spring容器中的實(shí)例對(duì)象,而不是使用默認(rèn)的this,因?yàn)橥ㄟ^(guò)bean實(shí)例的調(diào)用才會(huì)被spring的aop攔截
本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然后使用這個(gè)引用調(diào)用本地的方法即可達(dá)到被攔截的目的
備注:這種方法只能攔截protected,default,public方法,private方法無(wú)法攔截。這個(gè)是spring aop的一個(gè)機(jī)制。
2、如果不自定義異步方法的線程池默認(rèn)使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的線程池,這個(gè)類不重用線程,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程。并發(fā)大的時(shí)候會(huì)產(chǎn)生嚴(yán)重的性能問(wèn)題。
3、異步方法返回類型只能有兩種:void和java.util.concurrent.Future。
1)當(dāng)返回類型為void的時(shí)候,方法調(diào)用過(guò)程產(chǎn)生的異常不會(huì)拋到調(diào)用者層面,
可以通過(guò)注AsyncUncaughtExceptionHandler來(lái)捕獲此類異常
2)當(dāng)返回類型為Future的時(shí)候,方法調(diào)用過(guò)程產(chǎn)生的異常會(huì)拋到調(diào)用者層面
三、定義通用線程池
1、定義線程池
在Spring Boot主類中定義一個(gè)線程池,public Executor taskExecutor() 方法用于自定義自己的線程池,線程池前綴”taskExecutor-”。如果不定義,則使用系統(tǒng)默認(rèn)的線程池。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@EnableAsync
@Configuration
class TaskPoolConfig {
@Bean
public Executor taskExecutor1() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setCorePoolSize(5); //線程池活躍的線程數(shù)
pool.setMaxPoolSize(10); //線程池最大活躍的線程數(shù)
pool.setWaitForTasksToCompleteOnShutdown(true);
pool.setThreadNamePrefix("defaultExecutor");
return pool;
}
@Bean("taskExecutor")
public Executor taskExecutor2() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
}
上面我們通過(guò)ThreadPoolTaskExecutor創(chuàng)建了一個(gè)線程池,同時(shí)設(shè)置了如下參數(shù):
- 核心線程數(shù)10:線程池創(chuàng)建時(shí)初始化的線程數(shù)
- 最大線程數(shù)20:線程池最大的線程數(shù),只有在緩沖隊(duì)列滿了之后才會(huì)申請(qǐng)超過(guò)核心線程數(shù)的線程
- 緩沖隊(duì)列200:用來(lái)緩沖執(zhí)行任務(wù)的隊(duì)列
- 允許線程的空閑時(shí)間60秒:超過(guò)了核心線程數(shù)之外的線程,在空閑時(shí)間到達(dá)之后會(huì)被銷毀
- 線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
- 線程池對(duì)拒絕任務(wù)的處理策略:此處采用了CallerRunsPolicy策略,當(dāng)線程池沒(méi)有處理能力的時(shí)候,該策略會(huì)直接在execute方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù);如果執(zhí)行程序已被關(guān)閉,則會(huì)丟棄該任務(wù)
- 設(shè)置線程池關(guān)閉的時(shí)候等待所有任務(wù)都完成再繼續(xù)銷毀其他的Bean
- 設(shè)置線程池中任務(wù)的等待時(shí)間,如果超過(guò)這個(gè)時(shí)候還沒(méi)有銷毀就強(qiáng)制銷毀,以確保應(yīng)用最后能夠被關(guān)閉,而不是阻塞住
也可以單獨(dú)類來(lái)配置線程池:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Created by huangguisu on 2020/6/10.
*/
@Configuration
@EnableAsync
public class MyThreadPoolConfig {
private static final int CORE_POOL_SIZE = 10;
private static final int MAX_POOL_SIZE = 20;
private static final int QUEUE_CAPACITY = 200;
public static final String BEAN_EXECUTOR = "bean_executor";
/**
* 事件和情感接口線程池執(zhí)行器配置
* @return 事件和情感接口線程池執(zhí)行器bean
*
*/
@Bean(BEAN_EXECUTOR)
public Executor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
// 設(shè)置隊(duì)列容量
executor.setQueueCapacity(QUEUE_CAPACITY);
// 設(shè)置線程活躍時(shí)間(秒)
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("SE-Pool#Task");
// 設(shè)置拒絕策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
同時(shí)注意需要在配置類上添加@EnableAsync,當(dāng)然也可以在啟動(dòng)類上添加,表示開啟spring的@@Async
2、異步方法使用線程池
只需要在@Async注解中指定線程池名即可
@Component
public class Task {
//默認(rèn)使用線程池
@Async
public void doTaskOne() throws Exception {
System.out.println("開始做任務(wù)");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)耗時(shí):" + (end - start) + "毫秒");
}
//根據(jù)Bean Name指定特定線程池
@Async("taskExecutor")
public void doTaskOne() throws Exception {
System.out.println("開始做任務(wù)");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)耗時(shí):" + (end - start) + "毫秒");
}
}
3、通過(guò)xml配置定義線程池
Bean文件配置: spring_async.xml
1. 線程的前綴為xmlExecutor
2. 啟動(dòng)異步線程池配置
<!-- 等價(jià)于 @EnableAsync, executor指定線程池 -->
<task:annotation-driven executor="xmlExecutor"/>
<!-- id指定線程池產(chǎn)生線程名稱的前綴 -->
<task:executor
id="xmlExecutor"
pool-size="5-25"
queue-capacity="100"
keep-alive="120"
rejection-policy="CALLER_RUNS"/>
啟動(dòng)類導(dǎo)入xml文件:
@SpringBootApplication
@ImportResource("classpath:/async/spring_async.xml")
public class AsyncApplicationWithXML {
private static final Logger log = LoggerFactory.getLogger(AsyncApplicationWithXML.class);
public static void main(String[] args) {
log.info("Start AsyncApplication.. ");
SpringApplication.run(AsyncApplicationWithXML.class, args);
}
}
線程池參數(shù)說(shuō)明
1. ‘id' : 線程的名稱的前綴
2. ‘pool-size':線程池的大小。支持范圍”min-max”和固定值(此時(shí)線程池core和max sizes相同)
3. ‘queue-capacity' :排隊(duì)隊(duì)列長(zhǎng)度
4. ‘rejection-policy': 對(duì)拒絕的任務(wù)處理策略
5. ‘keep-alive' : 線程?;顣r(shí)間(單位秒)
四、異常處理
上面也提到:在調(diào)用方法時(shí),可能出現(xiàn)方法中拋出異常的情況。在異步中主要有有兩種異常處理方法:
1. 對(duì)于方法返回值是Futrue的異步方法:
a) 、一種是在調(diào)用future的get時(shí)捕獲異常;
b)、 在異常方法中直接捕獲異常
2. 對(duì)于返回值是void的異步方法:通過(guò)AsyncUncaughtExceptionHandler處理異常
@Component
public class AsyncException {
/**
* 帶參數(shù)的異步調(diào)用 異步方法可以傳入?yún)?shù)
* 對(duì)于返回值是void,異常會(huì)被AsyncUncaughtExceptionHandler處理掉
* @param s
*/
@Async
public void asyncInvokeWithException(String s) {
log.info("asyncInvokeWithParameter, parementer={}", s);
throw new IllegalArgumentException(s);
}
/**
* 異常調(diào)用返回Future
* 對(duì)于返回值是Future,不會(huì)被AsyncUncaughtExceptionHandler處理,需要我們?cè)诜椒ㄖ胁东@異常并處理
* 或者在調(diào)用方在調(diào)用Futrue.get時(shí)捕獲異常進(jìn)行處理
*
* @param i
* @return
*/
@Async
public Future<String> asyncInvokeReturnFuture(int i) {
System.out.println("asyncInvokeReturnFuture, parementer={}", i);
Future<String> future;
try {
Thread.sleep(1000 * 1);
future = new AsyncResult<String>("success:" + i);
throw new IllegalArgumentException("a");
} catch (InterruptedException e) {
future = new AsyncResult<String>("error");
} catch(IllegalArgumentException e){
future = new AsyncResult<String>("error-IllegalArgumentException");
}
return future;
}
}
實(shí)現(xiàn)AsyncConfigurer接口對(duì)異常線程池更加細(xì)粒度的控制
a) 創(chuàng)建線程自己的線程池
b) 對(duì)void方法拋出的異常處理的類AsyncUncaughtExceptionHandler
@Service
public class MyAsyncConfigurer implements AsyncConfigurer{
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
threadPool.setCorePoolSize(1);
threadPool.setMaxPoolSize(1);
threadPool.setWaitForTasksToCompleteOnShutdown(true);
threadPool.setAwaitTerminationSeconds(60 * 15);
threadPool.setThreadNamePrefix("MyAsync-");
threadPool.initialize();
return threadPool;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncExceptionHandler();
}
/**
* 自定義異常處理類
*/
class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
for (Object param : obj) {
System.out.println("Parameter value - " + param);
}
}
}
}
五、問(wèn)題
上面也提到:如果不自定義異步方法的線程池默認(rèn)使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的線程池,這個(gè)類不重用線程,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程。并發(fā)大的時(shí)候會(huì)產(chǎn)生嚴(yán)重的性能問(wèn)題。
一般的錯(cuò)誤OOM:OutOfMemoryError:unable to create new native thread,創(chuàng)建線程數(shù)量太多,占用內(nèi)存過(guò)大.
解決辦法:一般最好使用自定義線程池,做一些特殊策略, 比如自定義拒絕策略,如果隊(duì)列滿了,則拒絕處理該任務(wù)。
到此這篇關(guān)于Spring Boot之@Async異步線程池的文章就介紹到這了,更多相關(guān)Spring Boot @Async異步線程池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring?Boot異步線程間數(shù)據(jù)傳遞的四種方式
- springboot?正確的在異步線程中使用request的示例代碼
- SpringBoot使用異步線程池實(shí)現(xiàn)生產(chǎn)環(huán)境批量數(shù)據(jù)推送
- SpringBoot整合MQTT并實(shí)現(xiàn)異步線程調(diào)用的問(wèn)題
- SpringBoot?異步線程間傳遞上下文方式
- SpringBoot獲取HttpServletRequest的3種方式總結(jié)
- SpringBoot詳細(xì)講解異步任務(wù)如何獲取HttpServletRequest
- 在 Spring Boot 中使用異步線程時(shí)的 HttpServletRequest 復(fù)用問(wèn)題記錄
相關(guān)文章
Springboot集成RabbitMQ死信隊(duì)列的實(shí)現(xiàn)
在大多數(shù)的MQ中間件中,都有死信隊(duì)列的概念。本文主要介紹了Springboot集成RabbitMQ死信隊(duì)列的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
教你用Springboot實(shí)現(xiàn)攔截器獲取header內(nèi)容
項(xiàng)目中遇到一個(gè)需求,對(duì)接上游系統(tǒng)是涉及到需要增加請(qǐng)求頭,請(qǐng)求頭的信息是動(dòng)態(tài)獲取的,需要?jiǎng)討B(tài)從下游拿到之后轉(zhuǎn)給上游,文中非常詳細(xì)的介紹了該需求的實(shí)現(xiàn),需要的朋友可以參考下2021-05-05
IDEA 單元測(cè)試報(bào)錯(cuò):Class not found:xxxx springb
這篇文章主要介紹了IDEA 單元測(cè)試報(bào)錯(cuò):Class not found:xxxx springboot的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java 8新時(shí)間日期庫(kù)java.time的使用示例
這篇文章主要給你大家介紹了關(guān)于Java 8新時(shí)間日期庫(kù)java.time的使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
SpringBoot整合Swagger和Actuator的使用教程詳解
Swagger 是一套基于 OpenAPI 規(guī)范構(gòu)建的開源工具,可以幫助我們?cè)O(shè)計(jì)、構(gòu)建、記錄以及使用 Rest API。本篇文章主要介紹的是SpringBoot整合Swagger(API文檔生成框架)和SpringBoot整合Actuator(項(xiàng)目監(jiān)控)使用教程。感興趣的朋友一起看看吧2019-06-06
Java生成隨機(jī)時(shí)間的簡(jiǎn)單隨機(jī)算法
今天小編就為大家分享一篇關(guān)于Java生成隨機(jī)時(shí)間的簡(jiǎn)單隨機(jī)算法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
JDK10新特性之var泛型和多個(gè)接口實(shí)現(xiàn)方法
這篇文章主要介紹了JDK10的新特性:var泛型和多個(gè)接口實(shí)現(xiàn)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Java基于Session登錄驗(yàn)證的實(shí)現(xiàn)示例
基于Session的登錄驗(yàn)證方式是最簡(jiǎn)單的一種登錄校驗(yàn)方式,本文主要介紹了Java基于Session登錄驗(yàn)證的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02

