javascript
Springboot线程池的使用和扩展
????我們常用ThreadPoolExecutor提供的線程池服務,springboot框架提供了@Async注解,幫助我們更方便的將業務邏輯提交到線程池中異步執行,今天我們就來實戰體驗這個線程池服務;
實戰環境
- jdk 1.8
- springboot?2.1.4.RELEASE
- 開發工具:IntelliJ IDEA
實戰源碼
https://github.com/vincentduan/mavenProject.git,其中的threadpooldemoserver目錄
實戰步驟梳理
本次實戰的步驟如下:
?創建springboot工程
用IntelliJ IDEA創建一個springboot的web工程threadpooldemoserver,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>com.vincent</groupId><artifactId>threadpooldemoserver</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.1.4.RELEASE</version><configuration><mainClass>cn.ac.iie.App</mainClass></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><configuration><skip>true</skip></configuration></plugin></plugins></build></project>創建Service層的接口和實現
創建一個service層的接口AsyncService,如下:
public interface AsyncService {/*** 執行異步任務*/void executeAsync(); }對應的AsyncServiceImpl,實現如下:
@Service @Slf4j public class AsyncServiceImpl implements AsyncService {@Overridepublic void executeAsync() {log.info("start executeAsync");try{Thread.sleep(1000);}catch(Exception e){e.printStackTrace();}log.info("end executeAsync");} }這個方法做的事情很簡單:sleep了一秒鐘;
創建controller
創建一個controller為Hello,里面定義一個http接口,做的事情是調用Service層的服務,如下:
@RestController @Slf4j public class Hello {@Autowiredprivate AsyncService asyncService;@RequestMapping("/")public String submit() {log.info("start submit");//調用service層的任務asyncService.executeAsync();log.info("end submit");return "success";} }至此,我們已經做好了一個http請求的服務,里面做的事情其實是同步的,接下來我們就開始配置springboot的線程池服務,將service層做的事情都提交到線程池中去處理;
springboot的線程池配置
創建一個配置類ExecutorConfig,用來定義如何創建一個ThreadPoolTaskExecutor,要使用@Configuration和@EnableAsync這兩個注解,表示這是個配置類,并且是線程池的配置類,如下所示:
@Configuration @EnableAsync @Slf4j public class ExecutorConfig {@Beanpublic Executor asyncServiceExecutor() {log.info("start asyncServiceExecutor");ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//配置核心線程數executor.setCorePoolSize(5);//配置最大線程數executor.setMaxPoolSize(5);//配置隊列大小executor.setQueueCapacity(99999);//配置線程池中的線程的名稱前綴executor.setThreadNamePrefix("async-service-");// rejection-policy:當pool已經達到max size的時候,如何處理新任務// CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//執行初始化executor.initialize();return executor;} }注意,上面的方法名稱為asyncServiceExecutor,稍后馬上用到;
將Service層的服務異步化
打開AsyncServiceImpl.java,在executeAsync方法上增加注解@Async(“asyncServiceExecutor”),asyncServiceExecutor是前面ExecutorConfig.java中的方法名,表明executeAsync方法進入的線程池是asyncServiceExecutor方法創建的,如下:
@Service @Slf4j public class AsyncServiceImpl implements AsyncService {@Override@Async("asyncServiceExecutor")public void executeAsync() {log.info("start executeAsync");try{Thread.sleep(1000);}catch(Exception e){e.printStackTrace();}log.info("end executeAsync");} }驗證效果
?如上日志所示,我們可以看到controller的執行線程是"nio-8080-exec-5",這是tomcat的執行線程,而service層的日志顯示線程名為“async-service-1”,顯然已經在我們配置的線程池中執行了,并且每次請求中,controller的起始和結束日志都是連續打印的,表明每次請求都快速響應了,而耗時的操作都留給線程池中的線程去異步執行;
擴展ThreadPoolTaskExecutor
雖然我們已經用上了線程池,但是還不清楚線程池當時的情況,有多少線程在執行,多少在隊列中等待呢?這里我創建了一個ThreadPoolTaskExecutor的子類,在每次提交線程的時候都會將當前線程池的運行狀況打印出來,代碼如下:
@Slf4j public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {private void showThreadPoolInfo(String prefix){ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();if(null==threadPoolExecutor){return;}log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",this.getThreadNamePrefix(),prefix,threadPoolExecutor.getTaskCount(),threadPoolExecutor.getCompletedTaskCount(),threadPoolExecutor.getActiveCount(),threadPoolExecutor.getQueue().size());}@Overridepublic void execute(Runnable task) {showThreadPoolInfo("1. do execute");super.execute(task);}@Overridepublic void execute(Runnable task, long startTimeout) {showThreadPoolInfo("2. do execute");super.execute(task, startTimeout);}@Overridepublic Future<?> submit(Runnable task) {showThreadPoolInfo("1. do submit");return super.submit(task);}@Overridepublic <T> Future<T> submit(Callable<T> task) {showThreadPoolInfo("2. do submit");return super.submit(task);}@Overridepublic ListenableFuture<?> submitListenable(Runnable task) {showThreadPoolInfo("1. do submitListenable");return super.submitListenable(task);}@Overridepublic <T> ListenableFuture<T> submitListenable(Callable<T> task) {showThreadPoolInfo("2. do submitListenable");return super.submitListenable(task);} }如上所示,showThreadPoolInfo方法中將任務總數、已完成數、活躍線程數,隊列大小都打印出來了,然后Override了父類的execute、submit等方法,在里面調用showThreadPoolInfo方法,這樣每次有任務被提交到線程池的時候,都會將當前線程池的基本情況打印到日志中;
修改ExecutorConfig.java的asyncServiceExecutor方法,將ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor()改為ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor(),如下所示:
@Beanpublic Executor asyncServiceExecutor() {log.info("start asyncServiceExecutor");ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();//配置核心線程數executor.setCorePoolSize(5);//配置最大線程數executor.setMaxPoolSize(5);//配置隊列大小executor.setQueueCapacity(99999);//配置線程池中的線程的名稱前綴executor.setThreadNamePrefix("async-service-");// rejection-policy:當pool已經達到max size的時候,如何處理新任務// CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//執行初始化executor.initialize();return executor;}修改hello.java,方便查看線程池結果:
@RequestMapping("/")public Object submit() {log.info("start submit");//調用service層的任務asyncService.executeAsync();log.info("end submit");JSONObject jsonObject = new JSONObject();ThreadPoolExecutor threadPoolExecutor = visiableThreadPoolTaskExecutor.getThreadPoolExecutor();jsonObject.put("ThreadNamePrefix", visiableThreadPoolTaskExecutor.getThreadNamePrefix());jsonObject.put("TaskCount", threadPoolExecutor.getTaskCount());jsonObject.put("completedTaskCount", threadPoolExecutor.getCompletedTaskCount());jsonObject.put("activeCount", threadPoolExecutor.getActiveCount());jsonObject.put("queueSize", threadPoolExecutor.getQueue().size());return jsonObject;}再次啟動該工程,再瀏覽器反復刷新http://localhost:8080,看到的日志如下:
{"activeCount": 2,"queueSize": 1,"TaskCount": 26,"completedTaskCount": 23,"ThreadNamePrefix": "async-service-" }這時其中一條的返回結果。
這說明提交任務到線程池的時候,調用的是submit(Callable task)這個方法,當前已經提交了26個任務,完成了23個,當前有2個線程在處理任務,還剩1個任務在隊列中等待,線程池的基本情況一目了然;
總結
以上是生活随笔為你收集整理的Springboot线程池的使用和扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在10亿个整数中找出前1000个最大
- 下一篇: SpringBoot指定服务项目名