欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

Springboot线程池的使用和扩展

发布时间:2024/9/16 42 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Springboot线程池的使用和扩展 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

    我们常用ThreadPoolExecutor提供的线程池服务,springboot框架提供了@Async注解,帮助我们更方便的将业务逻辑提交到线程池中异步执行,今天我们就来实战体验这个线程池服务;

实战环境

  • jdk 1.8
  • springboot 2.1.4.RELEASE
  • 开发工具:IntelliJ IDEA

实战源码

https://github.com/vincentduan/mavenProject.git,其中的threadpooldemoserver目录

实战步骤梳理

本次实战的步骤如下:

  • 创建springboot工程;
  • 创建Service层的接口和实现;
  • 创建controller,开发一个http服务接口,里面会调用service层的服务;
  • 创建线程池的配置;
  • 将Service层的服务异步化,这样每次调用都会都被提交到线程池异步执行;
  • 扩展ThreadPoolTaskExecutor,在提交任务到线程池的时候可以观察到当前线程池的情况;
  •  创建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");} }

    验证效果

  • 将这个springboot运行起来(pom.xml所在文件夹下执行mvn spring-boot:run);
  • 在浏览器输入:http://localhost:8080;
  • 在浏览器用F5按钮快速多刷新几次;
  • 在springboot的控制台看见日志如下:
  • 2019-08-12 15:23:00.320 INFO 5848 --- [nio-8080-exec-2] cn.ac.iie.controller.Hello : start submit 2019-08-12 15:23:00.327 INFO 5848 --- [nio-8080-exec-2] cn.ac.iie.controller.Hello : end submit 2019-08-12 15:23:00.327 INFO 5848 --- [async-service-1] cn.ac.iie.service.impl.AsyncServiceImpl : start executeAsync 2019-08-12 15:23:01.329 INFO 5848 --- [async-service-1] cn.ac.iie.service.impl.AsyncServiceImpl : end executeAsync 2019-08-12 15:24:17.449 INFO 5848 --- [nio-8080-exec-5] cn.ac.iie.controller.Hello : start submit 2019-08-12 15:24:17.450 INFO 5848 --- [nio-8080-exec-5] cn.ac.iie.controller.Hello : end submit 2019-08-12 15:24:17.450 INFO 5848 --- [async-service-2] cn.ac.iie.service.impl.AsyncServiceImpl : start executeAsync 2019-08-12 15:24:18.125 INFO 5848 --- [nio-8080-exec-6] cn.ac.iie.controller.Hello : start submit 2019-08-12 15:24:18.126 INFO 5848 --- [nio-8080-exec-6] cn.ac.iie.controller.Hello : end submit 2019-08-12 15:24:18.128 INFO 5848 --- [async-service-3] cn.ac.iie.service.impl.AsyncServiceImpl : start executeAsync 2019-08-12 15:24:18.451 INFO 5848 --- [async-service-2] cn.ac.iie.service.impl.AsyncServiceImpl : end executeAsync 2019-08-12 15:24:18.685 INFO 5848 --- [nio-8080-exec-7] cn.ac.iie.controller.Hello : start submit 2019-08-12 15:24:18.688 INFO 5848 --- [nio-8080-exec-7] cn.ac.iie.controller.Hello : end submit 2019-08-12 15:24:18.703 INFO 5848 --- [async-service-4] cn.ac.iie.service.impl.AsyncServiceImpl : start executeAsync 2019-08-12 15:24:19.130 INFO 5848 --- [async-service-3] cn.ac.iie.service.impl.AsyncServiceImpl : end executeAsync 2019-08-12 15:24:19.704 INFO 5848 --- [async-service-4] cn.ac.iie.service.impl.AsyncServiceImpl : 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线程池的使用和扩展的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。