多线程的异步执行,虽然能大大的提高对计算机资源的利用,但是事情都有两面性,如果控制不好,反而会对系统造成重大影响,从而导致服务不可用。同时大量的线程回收也会给GC增加巨大压力。为了能够重复利用已创建的线程,降低创建和销毁线程的开销,jdk引入了线程池的概念,通俗的讲,当我们需要线程执行任务的时候,从线程池中获取线程,当我们完成任务后也不进行销毁,而将它归还给线程池供他人使用。Springboot也集成了线程池的功能,通过简单的注解就能实现任务的异步执行,下面我们将进行一次使用实践。
环境准备:
- windowns10;
- jdk1.8;
- springboot-2.1.6.RELEASE;
- 开发工具:IntelliJ IDEA;
实践步骤:
1.创建spring-boot项目
用IntelliJ IDEA创建一个springboot的web工程springboot-threadpool,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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ruby</groupId>
<artifactId>springboot-threadpool</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-threadpool</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.创建service层接口与实现
创建一个service层的接口AsyncService,如下:
package com.ruby.service;
/**
* @program: samples
* @description:
* @author: xf
* @create: 2019-08-17 20:53
**/
public interface AsyncService {
/**
* 执行异步任务
* 可以根据需求,自己加参数拟定,我这里就做个测试演示
*/
public void executeAsync(String input);
}
对应的AsyncServiceImpl,通过@Async注解,指定该方法为异步方法,该方法将会在独立的线程总执行,调用者不必等待它的结果完成,可进行其他工作,一般我们会将比较耗时的任务定为异步执行。
实现如下:
package com.ruby.service.impl;
import com.ruby.service.AsyncService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @program: samples
* @description:
* @author: xf
* @create: 2019-08-17 20:54
**/
@Service
public class AsyncServiceImpl implements AsyncService {
private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);
@Override
@Async("RubyTaskAsyncPool")
public void executeAsync(String input) {
logger.info("开始异步线程要做的事情");
logger.info("正在插入耗时数据[{}]",input);
try{
TimeUnit.SECONDS.sleep(5);
}catch (Exception e){}
logger.info("完成异步线程要做的事情");
}
}
这个方法我们让线程sleep 5秒,从而模拟线程处理耗时业务。
3.创建controller
创建一个controller为AsynTestController ,里面定义一个http接口,做的事情是调用Service层的服务,如下:
package com.ruby.controller;
import com.ruby.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: samples
* @description:
* @author: xf
* @create: 2019-08-17 20:55
**/
@RestController
@RequestMapping("/org/ruby/asynTest")
public class AsynTestController {
@Autowired
private AsyncService asyncService;
/**
* 功能描述: <br>
* <1.1 测试异步服务>
* @param:
* @return: void
*/
@GetMapping("/asyn")
public void async(){
for(int i=0;i<10;i++){
asyncService.executeAsync("helloWorld!");
}
}
}
4.定义线程池配置的bean
在spring中我们可以通过@Value注解获取.yml中配置信息,譬如域名配置spring.domain=www.spring.io,但是如果我们需要配置一组配置信息,并且进行引用,就需要使用@ConfigurationProperties注解,prefix指定这组配置文件是以什么开头。
package com.ruby.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @program: samples
* @description:
* @author: xf
* @create: 2019-08-17 20:51
**/
@Data
@ConfigurationProperties(prefix = "task.pool")
public class TaskThreadPoolConfig {
private int corePoolSize;
private int maxPoolSize;
private int keepAliveSeconds;
private int queueCapacity;
}
5.springboot线程池配置
创建一个配置类TaskExecutorPool,用来定义ThreadPoolTaskExecutor的创建过程,使用@Configuration和@EnableAsync这两个注解,表示这是个配置类,并且是线程池的配置类,如下所示:
package com.ruby.pool;
import com.ruby.config.TaskThreadPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
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.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @program: samples
* @description:
* @author: xf
* @create: 2019-08-17 20:52
**/
@Configuration
@EnableAsync
public class TaskExecutePool {
@Autowired
private TaskThreadPoolConfig config;
@Bean
public Executor RubyTaskAsyncPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程池大小
executor.setCorePoolSize(config.getCorePoolSize());
//最大线程数
executor.setMaxPoolSize(config.getMaxPoolSize());
//队列容量
executor.setQueueCapacity(config.getQueueCapacity());
//活跃时间
executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
//线程名字前缀
executor.setThreadNamePrefix("TestExecutor-");
// setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
// CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
6.springboot配置文件
corePoolSize:线程池的核心线程数
queueCapacity:线程池队列深度
maxPoolSize:最大线程数
具体参数的意义可以查看线程池相关配置说明
# 服务端口
server:
port: 18088
#线程池配置
task:
pool:
corePoolSize: 3
queueCapacity: 50
maxPoolSize: 5
keepAliveSeconds: 300
7.springboot启动类开启配置属性
为了将我们自定义的配置类bean注入spring容器,我们需要在springboot的启动类添加注解@EnableConfigurationProperties,该注解的使用,正是为了向spring容器添加@ConfigurationProperties注解修饰类的bean。
package com.ruby;
import com.ruby.config.TaskThreadPoolConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties({TaskThreadPoolConfig.class} ) // 开启配置属性支持
public class SpringbootThreadpoolApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootThreadpoolApplication.class, args);
}
}
8.通过postman模拟http连接测试
通过postman发送GET请求,发送地址如下:
http://localhost:18088/org/ruby/asynTest/asyn
我们会在控制台看到的日志如下,我们发现因为发送的请求任务较少,没有达到线程池队列最大数,所以仅创建了3个核心线程进行复用处理任务:
2019-08-17 21:15:23.373 INFO 17980 --- [ TestExecutor-1] com.ruby.service.impl.AsyncServiceImpl : 开始异步线程要做的事情
2019-08-17 21:15:23.373 INFO 17980 --- [ TestExecutor-2] com.ruby.service.impl.AsyncServiceImpl : 开始异步线程要做的事情
2019-08-17 21:15:23.374 INFO 17980 --- [ TestExecutor-3] com.ruby.service.impl.AsyncServiceImpl : 开始异步线程要做的事情
2019-08-17 21:15:23.373 INFO 17980 --- [ TestExecutor-2] com.ruby.service.impl.AsyncServiceImpl : 正在插入耗时数据[helloWorld!]
2019-08-17 21:15:23.373 INFO 17980 --- [ TestExecutor-1] com.ruby.service.impl.AsyncServiceImpl : 正在插入耗时数据[helloWorld!]
2019-08-17 21:15:23.374 INFO 17980 --- [ TestExecutor-3] com.ruby.service.impl.AsyncServiceImpl : 正在插入耗时数据[helloWorld!]
2019-08-17 21:15:28.377 INFO 17980 --- [ TestExecutor-1] com.ruby.service.impl.AsyncServiceImpl : 完成异步线程要做的事情
2019-08-17 21:15:28.377 INFO 17980 --- [ TestExecutor-2] com.ruby.service.impl.AsyncServiceImpl : 完成异步线程要做的事情
2019-08-17 21:15:28.377 INFO 17980 --- [ TestExecutor-3] com.ruby.service.impl.AsyncServiceImpl : 完成异步线程要做的事情
2019-08-17 21:15:28.377 INFO 17980 --- [ TestExecutor-2] com.ruby.service.impl.AsyncServiceImpl : 开始异步线程要做的事情
2019-08-17 21:15:28.377 INFO 17980 --- [ TestExecutor-3] com.ruby.service.impl.AsyncServiceImpl : 开始异步线程要做的事情
2019-08-17 21:15:28.377 INFO 17980 --- [ TestExecutor-2] com.ruby.service.impl.AsyncServiceImpl : 正在插入耗时数据[helloWorld!]
2019-08-17 21:15:28.378 INFO 17980 --- [ TestExecutor-1] com.ruby.service.impl.AsyncServiceImpl : 开始异步线程要做的事情
2019-08-17 21:15:28.378 INFO 17980 --- [ TestExecutor-3] com.ruby.service.impl.AsyncServiceImpl : 正在插入耗时数据[helloWorld!]
2019-08-17 21:15:28.378 INFO 17980 --- [ TestExecutor-1] com.ruby.service.impl.AsyncServiceImpl : 正在插入耗时数据[helloWorld!]
2019-08-17 21:15:33.378 INFO 17980 --- [ TestExecutor-2] com.ruby.service.impl.AsyncServiceImpl : 完成异步线程要做的事情