springboot自帶線程池ThreadPoolTaskExecutor使用
不管是阿里,還是華為java開發(fā)手冊(cè),都會(huì)有一條建議,就是讓開發(fā)者不要使用Executors去創(chuàng)建線程池,而是使用構(gòu)造函數(shù)ThreadPoolExecutor的方式來創(chuàng)建,并設(shè)置合理的參數(shù)。原因如下:
說明:Executors 返回的線程池對(duì)象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允許的請(qǐng)求隊(duì)列長度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM。
2) CachedThreadPool:
允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程,從而導(dǎo)致 OOM。
在spring框架中,spring提供了ThreadPoolTaskExecutor來創(chuàng)建線程池,該類在spring-context包下。其實(shí)ThreadPoolTaskExecutor是對(duì)ThreadPoolExecutor的封裝。
到了springboot這里,因?yàn)橐肓藄pring-boot-autoconfigurer,自動(dòng)裝配機(jī)制,在task包下,直接把ThreadPoolTaskExecutor注入到bean容器里面。所以在springboot項(xiàng)目中,如果要使用線程池,可以直接使用,都不用額外任何配置。
springboot自動(dòng)裝配的線程池使用的配置如下:
默認(rèn)核心線程數(shù)是8個(gè)。最大線程數(shù)和等待隊(duì)列都是Integer.MAX_VALUE。綜合上面的介紹,默認(rèn)配置的線程池其實(shí)也有OOM的風(fēng)險(xiǎn)。
這里使用的springboot版本是2.7.8。
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>springexample</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.8</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.20</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project>
異步任務(wù)類:在方法上添加@Async注解,可以讓他啟用線程池處理異步任務(wù)。
/* * xxx co.ltd Copyright @ 2023-2023 All Rights Reserved */ package com.example.task; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.util.Random; import java.util.concurrent.CompletableFuture; /** * 描述信息 * * @author Administrator * @since 2023/4/2 7:30 */ @Slf4j @Component public class AsyncTask { private Random random = new Random(); @Async public CompletableFuture<String> doTaskOne() throws Exception { log.info("task one start."); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("task one done,cost " + (end - start) + "ms."); return CompletableFuture.completedFuture("task one done"); } @Async public CompletableFuture<String> doTaskTwo() throws Exception { log.info("task two start."); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("task two done,cost " + (end - start) + "ms."); return CompletableFuture.completedFuture("task two done"); } @Async public CompletableFuture<String> doTaskThree() throws Exception { log.info("task three start."); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); log.info("task three done,cost " + (end - start) + "ms."); return CompletableFuture.completedFuture("task three done"); } }
啟動(dòng)類:啟動(dòng)類上添加注解@EnableAsync開啟異步
/* * xxx co.ltd Copyright @ 2023-2023 All Rights Reserved */ package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; /** * 描述信息 * * @author Administrator * @since 2023/4/2 7:29 */ @SpringBootApplication @EnableAsync public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
測試類:
/* * xxx co.ltd Copyright @ 2023-2023 All Rights Reserved */ package com.example.task; import com.example.App; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.concurrent.CompletableFuture; /** * 描述信息 * * @author Administrator * @since 2023/4/2 7:34 */ @SpringBootTest(classes = {App.class}) @Slf4j public class AsyncTaskTest { @Autowired private AsyncTask asyncTask; @Test public void testTask() throws Exception { long start = System.currentTimeMillis(); CompletableFuture<String> taskOne = asyncTask.doTaskOne(); CompletableFuture<String> taskTwo = asyncTask.doTaskTwo(); CompletableFuture<String> taskThree = asyncTask.doTaskThree(); CompletableFuture.allOf(taskOne, taskTwo, taskThree).join(); long end = System.currentTimeMillis(); log.info("all task done,cost " + (end - start) + " ms"); } }
這里設(shè)置了3個(gè)任務(wù),默認(rèn)線程池核心線程數(shù)是8個(gè), 所以這3個(gè)任務(wù)在線程池環(huán)境中,基本都是同時(shí)運(yùn)行,所以總體運(yùn)行時(shí)間肯定會(huì)大于他們3各種最耗時(shí)的一個(gè)任務(wù),小于三個(gè)任務(wù)耗時(shí)之和。
運(yùn)行這個(gè)測試用例,打印結(jié)果:
從打印結(jié)果來看,3個(gè)任務(wù)幾乎同時(shí)執(zhí)行,運(yùn)行結(jié)束,分別耗時(shí):2094ms、 653ms、 1505ms,最后總耗時(shí)2129ms,符合預(yù)期。
這里打印的線程池前綴是task-,也是默認(rèn)線程池配置。
在springboot配置中,提供了可以配置線程池的參數(shù):
spring: task: execution: pool: core-size: 2 max-size: 5 queue-capacity: 10 thread-name-prefix: test-task-
這些參數(shù)都不是我們自定義的,而是springboot配置文件中指定的參數(shù)名。所以我們可以通過yml自動(dòng)提示類進(jìn)行配置:
這里也可以看出默認(rèn)線程池配置核心數(shù)量是8個(gè), 這里我們?cè)O(shè)置為2,來驗(yàn)證線程池工作原理。
這里有3個(gè)任務(wù),核心線程數(shù)是2,所以只能先執(zhí)行2個(gè)任務(wù),剩下的進(jìn)入隊(duì)列等待,當(dāng)前面一個(gè)任務(wù)執(zhí)行完成,最后一個(gè)任務(wù)才會(huì)從等待隊(duì)列中進(jìn)入核心線程進(jìn)行執(zhí)行,重新運(yùn)行單元測試,打印信息如下:
這個(gè)打印結(jié)果,剛開始任務(wù)1,2都運(yùn)行,任務(wù)2完成之后,任務(wù)3開始執(zhí)行。
因?yàn)樾薷牧司€程前綴,這里打印的線程前綴是test-task-,從線程前綴 + 線程數(shù)上來看,這里最大線程數(shù)是2,因?yàn)榍懊嬖O(shè)置的核心線程數(shù)就是2。
相信做過springboot線程池相關(guān)的測試,可能有的人得出的結(jié)論和我這里不太一樣,springboot默認(rèn)線程池是SimpleAsyncTaskExecutor。這個(gè)原因呢,有兩個(gè),一個(gè)是springboot版本的原因,默認(rèn)不做任何配置,一樣的代碼,上面運(yùn)行打印的線程池前綴就是SimpleAsyncTaskExecutor。另外一個(gè)原因就是上面提到的ThreadPoolTaskExecutor在配置的時(shí)候,其實(shí)使用了一個(gè)特別的注解:@ConditionalOnMissingBean({Executor.class}),如下所示:
這個(gè)注解的意思是,當(dāng)bean容器中沒有Executor.class這個(gè)實(shí)例的時(shí)候,進(jìn)行配置。也即是說其他地方配置了線程器Executor,那么這個(gè)ThreadPoolTaskExecutor的bean就不會(huì)被配置。這也就是大家的結(jié)論里面spring線程池默認(rèn)不是ThreadPoolTaskExecutor的原因。
我這里通過引入spring-boot-starter-websocket依賴,然后配置websocket:
/* * xxx co.ltd Copyright @ 2023-2023 All Rights Reserved */ package com.example.config; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; /** * 描述信息 * * @author Administrator * @since 2023/4/2 20:14 */ @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/tmax/ws").setAllowedOriginPatterns("*").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/user","/topic"); registry.setApplicationDestinationPrefixes("/app"); registry.setUserDestinationPrefix("/user"); } }
同樣的,我們?cè)俅螠y試,發(fā)現(xiàn)結(jié)果如下所示:
這里也驗(yàn)證了上面@ConditionalOnMissingBean({Executor.class})注解的作用,因?yàn)橛辛藙e的線程池,所以這里ThreadPoolTaskExecutor線程池就沒有被加載。這里的線程池就是SimpleAsyncTaskExecutor。這個(gè)線程池其實(shí)不是一個(gè)真正的線程池,因?yàn)樗看味紩?huì)創(chuàng)建新線程,這個(gè)線程池創(chuàng)建的目的其實(shí)就是為了執(zhí)行少量短時(shí)間的任務(wù),并不適合在高并發(fā)場景下。
通過上面的實(shí)驗(yàn),我們知道,在springboot 2.7.8版本里面,如果沒有其他配置,默認(rèn)線程池就是ThreadPoolTaskExecutor,而且可以不用任何配置就可以使用。但是它還是有OOM的風(fēng)險(xiǎn),因?yàn)樗膍ax-size和queue-capacity都是Integer.MAX_VALUE,所以我們需要修改它的默認(rèn)線程池配置信息。但是默認(rèn)線程池有個(gè)德性,就是如果配置了其他線程池,它又不會(huì)被加載。
所以一般的項(xiàng)目里面,我們都是進(jìn)行如下所示的手動(dòng)配置:
/* * xxx co.ltd Copyright @ 2023-2023 All Rights Reserved */ package com.example.config; 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.ThreadPoolExecutor; /** * 描述信息 * * @author Administrator * @since 2023/4/2 20:36 */ @Configuration @EnableAsync public class ThreadPoolConfig { @Value("${spring.task.execution.pool.core-size}") private int corePoolSize; @Value("${spring.task.execution.pool.max-size}") private int maxPoolSize; @Value("${spring.task.execution.pool.queue-capacity}") private int queueCapacity; @Value("${spring.task.execution.thread-name-prefix}") private String threadNamePrefix; @Bean public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setThreadNamePrefix(threadNamePrefix); // 拒絕策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); executor.initialize(); return executor; } }
另外,一定要結(jié)合application.yml中設(shè)置的線程池配置信息使用,這樣才符合文章開頭所說的大廠java開發(fā)手冊(cè)中建議使用自定義參數(shù)配置線程池,避免OOM風(fēng)險(xiǎn)。
到此這篇關(guān)于springboot自帶線程池ThreadPoolTaskExecutor使用的文章就介紹到這了,更多相關(guān)springboot ThreadPoolTaskExecutor 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot結(jié)合Redis實(shí)現(xiàn)緩存
本文主要介紹了SpringBoot結(jié)合Redis實(shí)現(xiàn)緩存,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06java 中動(dòng)態(tài)代理機(jī)制的實(shí)例講解
這篇文章主要介紹了java 中動(dòng)態(tài)代理機(jī)制的實(shí)例講解的相關(guān)資料,希望通過本文大家能夠理解掌握動(dòng)態(tài)代理機(jī)制,需要的朋友可以參考下2017-09-09Java存儲(chǔ)過程調(diào)用CallableStatement的方法
這篇文章主要介紹了Java存儲(chǔ)過程調(diào)用CallableStatement的方法,幫助大家更好的理解和學(xué)習(xí)Java,感興趣的朋友可以了解下2020-11-11使用json字符串插入節(jié)點(diǎn)或者覆蓋節(jié)點(diǎn)
這篇文章主要介紹了使用json字符串插入節(jié)點(diǎn)或者覆蓋節(jié)點(diǎn)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Java?詳解Collection集合之ArrayList和HashSet
本章具體介紹了ArrayList和HashSet兩種集合的基本使用方法和區(qū)別,圖解穿插代碼實(shí)現(xiàn)。?JAVA成仙路從基礎(chǔ)開始講,后續(xù)會(huì)講到JAVA高級(jí),中間會(huì)穿插面試題和項(xiàng)目實(shí)戰(zhàn),希望能給大家?guī)韼椭?/div> 2022-03-03Java項(xiàng)目Guava包?HashMultimap使用及注意事項(xiàng)
guava基本上可以說是java開發(fā)項(xiàng)目中,大概率會(huì)引入的包,今天介紹的主角是一個(gè)特殊的容器HashMultmap,可以簡單的將它的數(shù)據(jù)結(jié)構(gòu)理解為Map<K,?Set<V>>,今天主要介紹下基礎(chǔ)的知識(shí)點(diǎn)?HashMultmap級(jí)使用,感興趣的朋友一起看看吧2022-05-05Springboot基于enable模塊驅(qū)動(dòng)的實(shí)現(xiàn)
這篇文章主要介紹了Springboot基于enable模塊驅(qū)動(dòng)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08jmeter中json提取器如何提取多個(gè)參數(shù)值
關(guān)于jmeter中的正則表達(dá)式及json提取器可以提取響應(yīng)值,但是實(shí)際可以需要上個(gè)接口的多個(gè)響應(yīng)值,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下2021-11-11最新評(píng)論