SpringBoot中的異步多線程使用及避坑指南
在現(xiàn)代的Web應(yīng)用開發(fā)中,處理請(qǐng)求時(shí)需要考慮到系統(tǒng)的性能和響應(yīng)速度。特別是在處理大量請(qǐng)求或者需要進(jìn)行耗時(shí)操作時(shí),采用異步多線程處理是一種常見的解決方案。Spring Boot提供了@Async注解來(lái)支持異步方法調(diào)用,結(jié)合合適的線程池配置,可以很容易地實(shí)現(xiàn)異步多線程處理,提升系統(tǒng)的并發(fā)能力和性能。
今日內(nèi)容介紹,大約花費(fèi)9分鐘
圖片
1.配置線程池
@Configuration
@EnableAsync
public class AsyncConfiguration {
@Bean("doSomethingExecutor")
public Executor doSomethingExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心線程數(shù):線程池創(chuàng)建時(shí)候初始化的線程數(shù)
executor.setCorePoolSize(10);
// 最大線程數(shù):線程池最大的線程數(shù),只有在緩沖隊(duì)列滿了之后才會(huì)申請(qǐng)超過(guò)核心線程數(shù)的線程
executor.setMaxPoolSize(20);
// 緩沖隊(duì)列:用來(lái)緩沖執(zhí)行任務(wù)的隊(duì)列大小
executor.setQueueCapacity(500);
// 允許線程的空閑時(shí)間60秒:當(dāng)超過(guò)了核心線程之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷毀
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("do-something-");
// 緩沖隊(duì)列滿了之后的拒絕策略:由調(diào)用線程處理(一般是主線程
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
executor.initialize();
return executor;
}
}
在這個(gè)配置中,我們使用了ThreadPoolTaskExecutor作為線程池的實(shí)現(xiàn),并且設(shè)置了一些關(guān)鍵參數(shù),如核心線程數(shù)、最大線程數(shù)、緩沖隊(duì)列大小等。如果不太了解線程池的小伙伴可以看一下之前介紹線程池介紹線程池的核心參數(shù),線程池的執(zhí)行原理知道
2. @Async注解
在需要異步執(zhí)行的方法上使用@Async注解。這樣的方法將會(huì)在一個(gè)單獨(dú)的線程中執(zhí)行,而不會(huì)阻塞主線程。
@Slf4j
@Service
public class AsyncService {
// 指定使用beanname為doSomethingExecutor的線程池
@Async("doSomethingExecutor")
public CompletableFuture<String> doSomething(String message) throws InterruptedException {
log.info("doSomethingExecutor thread name ={}", Thread.currentThread().getName());
Thread.sleep(1000);
return CompletableFuture.completedFuture(message);
}
}
doSomething()方法被標(biāo)記為異步方法,并且指定了使用名為"doSomethingExecutor"的線程池進(jìn)行執(zhí)行。
3. 異步多結(jié)果聚合返回CompletableFuture
在某些情況下,我們可能需要等待多個(gè)異步任務(wù)執(zhí)行完畢后再進(jìn)行下一步操作,這時(shí)可以使用CompletableFuture來(lái)實(shí)現(xiàn)異步多結(jié)果的聚合。
@RestController
@RequestMapping
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/open/somethings")
public List<String> somethings() throws InterruptedException {
int count = 6;
List<CompletableFuture<String>> futures = new ArrayList<>();
List<String> results = new ArrayList<>();
// 啟動(dòng)多個(gè)異步任務(wù),并將 CompletableFuture 對(duì)象存儲(chǔ)在列表中
for (int i = 1; i < count; i++) {
CompletableFuture<String> future = asyncService.doSomething("index: "+i);
futures.add(future);
}
for (CompletableFuture<String> future : futures) {
String result = future.get(); // 阻塞等待異步任務(wù)完成并獲取結(jié)果
results.add(result);
}
return results;
}
}
我們通過(guò)循環(huán)啟動(dòng)了多個(gè)異步任務(wù),將返回的 CompletableFuture 對(duì)象存儲(chǔ)在列表中。然后,我們?cè)俅窝h(huán)遍歷這些 CompletableFuture 對(duì)象,并調(diào)用 get() 方法來(lái)阻塞等待異步任務(wù)完成,獲取結(jié)果。最后,將結(jié)果添加到結(jié)果列表中并返回
4. 測(cè)試
使用瀏覽器發(fā)送http://localhost:8888/open/somethings,結(jié)果如下
圖片
發(fā)現(xiàn)使用多個(gè)線程執(zhí)行方法
圖片
5.注意事項(xiàng)
@Async注解會(huì)在以下幾個(gè)場(chǎng)景失效,使用了@Async注解,但就沒(méi)有走多線程:
- 異步方法使用static關(guān)鍵詞修飾;
- 異步類不是一個(gè)Spring容器的bean(一般使用注解@Component和@Service,并且能被Spring掃描到);
- SpringBoot應(yīng)用中沒(méi)有添加@EnableAsync注解;
- 在同一個(gè)類中,一個(gè)方法調(diào)用另外一個(gè)有@Async注解的方法,注解不會(huì)生效。原因是@Async注解的方法,是在代理類中執(zhí)行的。
異步方法使用注解@Async的返回值只能為void或者Future及其子類,當(dāng)返回結(jié)果為其他類型時(shí),方法還是會(huì)異步執(zhí)行,但是返回值都是null