springboot中线程池使用实践

多线程的异步执行,虽然能大大的提高对计算机资源的利用,但是事情都有两面性,如果控制不好,反而会对系统造成重大影响,从而导致服务不可用。同时大量的线程回收也会给GC增加巨大压力。为了能够重复利用已创建的线程,降低创建和销毁线程的开销,jdk引入了线程池的概念,通俗的讲,当我们需要线程执行任务的时候,从线程池中获取线程,当我们完成任务后也不进行销毁,而将它归还给线程池供他人使用。Springboot也集成了线程池的功能,通过简单的注解就能实现任务的异步执行,下面我们将进行一次使用实践。

环境准备:

  1. windowns10;
  2. jdk1.8;
  3. springboot-2.1.6.RELEASE;
  4. 开发工具: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   : 完成异步线程要做的事情

 


版权声明:本文为blacktulip_feng原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。