前言?
? ? ? ? 在項(xiàng)目應(yīng)用中,使用MQ異步調(diào)用來(lái)實(shí)現(xiàn)系統(tǒng)性能優(yōu)化,完成服務(wù)間數(shù)據(jù)同步是常用的技術(shù)手段。如果是在同一臺(tái)服務(wù)器內(nèi)部,不涉及到分布式系統(tǒng),單純的想實(shí)現(xiàn)部分業(yè)務(wù)的異步執(zhí)行,這里介紹一個(gè)更簡(jiǎn)單的異步方法調(diào)用。
????????對(duì)于異步方法調(diào)用,從Spring3 開(kāi)始提供了@Async 注解,該注解可以被標(biāo)注在方法上,以便異步地調(diào)用該方法。調(diào)用者將在調(diào)用時(shí)立即返回,而方法的實(shí)際執(zhí)行將提交給 Spring TaskExecutor 的任務(wù)中,由指定的線程池中的線程執(zhí)行。
????????本文講述了@Async注解在Spring體系中的簡(jiǎn)單應(yīng)用,僅供學(xué)習(xí),歡迎意見(jiàn)反饋。??
正文
一、Spring線程池的分類(lèi)
????????以下是官方已經(jīng)實(shí)現(xiàn)的常見(jiàn)的5個(gè)TaskExecuter。Spring 宣稱(chēng)對(duì)于任何場(chǎng)景,這些TaskExecuter完全夠用了:
線程 | 特點(diǎn) |
---|---|
SimpleAsyncTaskExecutor | 每次請(qǐng)求新開(kāi)線程,沒(méi)有最大線程數(shù)設(shè)置.不是真的線程池,這個(gè)類(lèi)不重用線程,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程 |
SyncTaskExecutor | 不是異步的線程。同步可以用SyncTaskExecutor,但這個(gè)可以說(shuō)不算一個(gè)線程池,因?yàn)檫€在原線程執(zhí)行。這個(gè)類(lèi)沒(méi)有實(shí)現(xiàn)異步調(diào)用,只是一個(gè)同步操作。 |
ConcurrentTaskExecutor | Executor的適配類(lèi),不推薦使用。如果ThreadPoolTaskExecutor不滿(mǎn)足要求時(shí),才用考慮使用這個(gè)類(lèi)。 |
SimpleThreadPoolTaskExecutor | 是Quartz的SimpleThreadPool的類(lèi)。線程池同時(shí)被quartz和非quartz使用,才需要使用此類(lèi)。 |
ThreadPoolTaskExecutor | 最常使用,推薦,是阿里巴巴Java開(kāi)發(fā)規(guī)范中指定的線程類(lèi),要求jdk版本大于等于5。其實(shí)質(zhì)是對(duì)java.util.concurrent.ThreadPoolExecutor 的包裝。 |
???????參考阿里巴巴java開(kāi)發(fā)規(guī)范,?在線程池應(yīng)用中:線程池不允許使用Executors去創(chuàng)建,也不允許使用系統(tǒng)默認(rèn)的線程池,推薦通過(guò) ThreadPoolExecutor 的方式,這樣的處理方式讓開(kāi)發(fā)的工程師更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
二、SpringBoot中使用@Async
? ? ? ? 使用異步線程調(diào)用方法,過(guò)程如下:
- 編寫(xiě)配置類(lèi),定義線程池
- 啟動(dòng)類(lèi)/配置文件上加上注解:@EnableAsync
- 方法上加上注解:@Async
? ? ? ? 下面演示案例中,我本地項(xiàng)目的目錄,僅供參考:
2.1 啟用@Async
? ? ? ? 關(guān)鍵注解?@EnableAsync ?。?!可以加載啟動(dòng)類(lèi)上,也可以加在配置文件上,效果是一樣的。
- ?方式一:基于Springboot啟動(dòng)類(lèi)啟用
@EnableAsync
@SpringBootApplication
public class AsyncApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncApplication.class, args);
}
}
- 方式二:基于Java配置的啟用
// com.example.async.service 為即將開(kāi)啟異步線程業(yè)務(wù)的包位置(后面有詳細(xì)講解)
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return executor();
}
...
}
2.2 @Async與線程池
????????Spring應(yīng)用默認(rèn)的線程池,指在@Async注解在使用時(shí),不指定線程池的名稱(chēng)。查看源碼,@Async的默認(rèn)線程池為SimpleAsyncTaskExecutor。
@Slf4j
@Service
public class BusinessServiceImpl implements BusinessService {
/**
* 方法4:沒(méi)有指定線程池,驗(yàn)證默認(rèn)線程池也ok(不推薦:規(guī)避資源耗盡的風(fēng)險(xiǎn))
*/
@Async
public void asyncDemo4() {
log.info("asyncDemo4:" + Thread.currentThread().getName() + " 正在執(zhí)行 ----------");
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("asyncDemo4:" + Thread.currentThread().getName() + " 執(zhí)行結(jié)束?。?);
}
}
2.3 @Async自定義線程池
????????自定義線程池,可對(duì)系統(tǒng)中線程池更加細(xì)粒度的控制,方便調(diào)整線程池大小配置,線程執(zhí)行異??刂坪吞幚?。在設(shè)置系統(tǒng)自定義線程池代替默認(rèn)線程池時(shí),雖可通過(guò)多種模式設(shè)置,但替換默認(rèn)線程池最終產(chǎn)生的線程池有且只能設(shè)置一個(gè)(不能設(shè)置多個(gè)類(lèi)繼承AsyncConfigurer)。
????????自定義線程池有如下模式:
- 重新實(shí)現(xiàn)接口AsyncConfigurer;
- 繼承AsyncConfigurerSupport;
- 配置由自定義的TaskExecutor替代內(nèi)置的任務(wù)執(zhí)行器;
? ? ? ? 三者使用方式大體相同,下面的案例將展示說(shuō)明其一:實(shí)現(xiàn)接口AsyncConfigurer接口的方式。
- 配置一個(gè)線程池?ThreadPoolTaskExecutor
/**
* com.example.async.service:即將開(kāi)啟異步線程的業(yè)務(wù)方法是哪個(gè)
*
* 解釋?zhuān)? * 1.即將開(kāi)啟異步線程業(yè)務(wù)的包位置:com.example.async.service
* 2.通過(guò) ThreadPoolExecutor 的方式,規(guī)避資源耗盡的風(fēng)險(xiǎn)
*/
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return executor();
}
/**
* 執(zhí)行需要依賴(lài)線程池,這里就來(lái)配置一個(gè)線程池
* 1.當(dāng)池子大小小于corePoolSize,就新建線程,并處理請(qǐng)求
* 2.當(dāng)池子大小等于corePoolSize,把請(qǐng)求放入workQueue(QueueCapacity)中,池子里的空閑線程就去workQueue中取任務(wù)并處理
* 3.當(dāng)workQueue放不下任務(wù)時(shí),就新建線程入池,并處理請(qǐng)求,如果池子大小撐到了maximumPoolSize,就用RejectedExecutionHandler來(lái)做拒絕處理
* 4.當(dāng)池子的線程數(shù)大于corePoolSize時(shí),多余的線程會(huì)等待keepAliveTime長(zhǎng)時(shí)間,如果無(wú)請(qǐng)求可處理就自行銷(xiāo)毀
*/
@Bean("asyncExecutor")
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//設(shè)置線程名
executor.setThreadNamePrefix("async-method-execute-");
//設(shè)置核心線程數(shù)
executor.setCorePoolSize(10);
//設(shè)置最大線程數(shù)
executor.setMaxPoolSize(50);
//線程池所使用的緩沖隊(duì)列
executor.setQueueCapacity(100);
//設(shè)置多余線程等待的時(shí)間,單位:秒
executor.setKeepAliveSeconds(10);
// 初始化線程
executor.initialize();
return executor;
}
}
- 執(zhí)行異步線程方法,指定線程池:value 要與配置類(lèi)?Bean() 中的name相同?
/**
* 異步線程 - 執(zhí)行業(yè)務(wù)
* 注意:
* 1.@Async 注解調(diào)用用線程池,不指定的話默認(rèn):SimpleAsyncTaskExecutor
* 2.SimpleAsyncTaskExecutor 不是真的線程池,這個(gè)類(lèi)不重用線程,默認(rèn)每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程
*/
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
/**
* 方法1:@Async 標(biāo)注為異步任務(wù):執(zhí)行此方法的時(shí)候,會(huì)單獨(dú)開(kāi)啟線程來(lái)執(zhí)行,不影響主線程的執(zhí)行
*/
@Async("asyncExecutor")
public void asyncDemo1() {
log.info("asyncDemo1:" + Thread.currentThread().getName() + " 正在執(zhí)行 ----------");
// 故意等10秒,那么異步線程開(kāi)起來(lái),這樣明顯看到:方法2不用等方法1執(zhí)行完就調(diào)用了
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("asyncDemo1:" + Thread.currentThread().getName() + " 執(zhí)行結(jié)束??!");
}
/**
* 方法2:與方法1一起執(zhí)行,證明2個(gè)線程異步執(zhí)行,互不干擾
*/
@Async("asyncExecutor")
public void asyncDemo2() {
log.info("asyncDemo2:" + Thread.currentThread().getName() + " 正在執(zhí)行 ----------");
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("asyncDemo2:" + Thread.currentThread().getName() + " 執(zhí)行結(jié)束??!");
}
/**
* 方法3:沒(méi)有指定線程池,驗(yàn)證默認(rèn)線程池也ok(不推薦:規(guī)避資源耗盡的風(fēng)險(xiǎn))
*/
@Async
public void asyncDemo3() {
log.info("asyncDemo3:" + Thread.currentThread().getName() + " 正在執(zhí)行 ----------");
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("asyncDemo3:" + Thread.currentThread().getName() + " 執(zhí)行結(jié)束?。?);
}
}
2.4 啟動(dòng)測(cè)試
? ? ? ? 通過(guò) AsyncApplication 啟動(dòng) SpringBoot 項(xiàng)目,Postman 進(jìn)行接口測(cè)試:
http://127.0.0.1:8080/async/demo文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-423536.html
- 我寫(xiě)了4個(gè)demo,分別模擬4種情況,詳情在注釋里有寫(xiě)。
@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncControllor {
@Autowired
private AsyncService asyncMethodService;
@Autowired
private BusinessService businessService;
@GetMapping("/demo")
public String demo() {
log.info("接口調(diào)用:【開(kāi)始】 --------------------");
try {
// 執(zhí)行異步任務(wù) - 自定義線程池
asyncMethodService.asyncDemo1();
asyncMethodService.asyncDemo2();
asyncMethodService.asyncDemo3();
// 執(zhí)行異步任務(wù) - 默認(rèn)線程池
businessService.asyncDemo4();
} catch (Exception e) {
return "Exception";
}
log.info("接口調(diào)用:【結(jié)束】 --------------------");
return "success";
}
}
- 運(yùn)行結(jié)果:接口執(zhí)行結(jié)束,異步線程仍在運(yùn)行
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-423536.html
總結(jié)
- @EnableAsync 是啟動(dòng) @Async 異步線程的關(guān)鍵,可以加載啟動(dòng)類(lèi)上,也可以加在配置文件上;
- 為了規(guī)避資源耗盡的風(fēng)險(xiǎn),推薦通過(guò) ThreadPoolExecutor 的方式創(chuàng)建線程池;
- @Async 注解標(biāo)注在方法上,以便異步地調(diào)用該方法;
到了這里,關(guān)于@Async異步線程:Spring 自帶的異步解決方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!