【随笔】Springboot配置线程池

前言

在开发中我们一般都是使用JUC包中的ThreadPoolExecutor的类,但在Springboot项目环境中可以使用ThreadPoolTaskExecutor类完成线程池的声明定义,且还可以使用@Async注解标注在接口实现方法上说明该逻辑异步处理。但是我们在使用的时候务必要进行相应环境配置,否则会存在一些问题,如默认值corePoolSize=1就相当于单线程,queyeCapacity=Integer.MAX_VALUE容易导致OOM问题等。

一、声明线程配置

说明:可以在应用配置直接添加配置属性,我是独立到单独配置文件thread-pool.properties中


#配置核心线程数
thread-pool.corePoolSize = 4

# 配置最大线程数
thread-pool.maxPoolSize = 100

# 配置队列大小
thread-pool.queueCapacity = 500

# 线程最多存活时间/s
thread-pool.keepAliveSeconds = 120

# 配置线程池中的线程的名称前缀
thread-pool.threadNamePrefix = my-thread-

二、配置属性读取

声明一个配置属性对应的javabean,使用@ConfigurationProperties完成配置读取

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 线程池参数配置
 *
 * @author darrn.xiang
 * @date 2022/8/21 17:22
 */
@Data
@ConfigurationProperties(prefix = "thread-pool")
public class MyThreadPoolProperties {

    private int  corePoolSize;

    private int  maxPoolSize;

    private int  queueCapacity;

    private String  threadNamePrefix;

    private int keepAliveSeconds;
}

三、声明线程池实例bean并设置属性

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 配置线程池实例
 *
 * @author darrn.xiang
 * @date 2022/8/21 17:26
 */
@Configuration
@PropertySource(name="thread-pool",value = {"classpath:thread-pool.properties"})
@EnableConfigurationProperties(MyThreadPoolProperties.class)
public class MyThreadPoolConfiguration {

    @Bean("myThreadExecutor")
    public ThreadPoolTaskExecutor myThreadExecutor(MyThreadPoolProperties properties){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(properties.getCorePoolSize());
        executor.setMaxPoolSize(properties.getMaxPoolSize());
        executor.setQueueCapacity(properties.getQueueCapacity());
        executor.setThreadNamePrefix(properties.getThreadNamePrefix());

        executor.setKeepAliveSeconds(properties.getKeepAliveSeconds());

        // 拒绝策略,交给调用这线程处理
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 初始化
        executor.initialize();

        return executor;
    }
}

四、验证业务中线程池的使用

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * 线程池测试
 *
 * @author darrn.xiang
 * @date 2022/8/21 17:35
 */
@RestController
@RequestMapping("/api/pools")
public class TestThreadPoolController {

    @Autowired
    @Qualifier("myThreadExecutor")
    private ThreadPoolTaskExecutor executor;

    @GetMapping("/test1")
    public String test1() throws ExecutionException, InterruptedException {

        // 无返回值
        executor.execute(() -> System.out.println("任务被执行了"));

        // 有返回值
        Future<String> result = executor.submit(() -> "同步任务执行成功");
        System.out.println(result.get());

        return "SUCCESS";
    }
}

五、验证使用@Async注解标注异步任务

import com.imk.cases.exceptions.handling.util.AppContextUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

// 启动异步任务和定时任务
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class CaseExceptionHandlingApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(CaseExceptionHandlingApplication.class, args);
        AppContextUtils.setApplicationContext(applicationContext);
    }

}
// 定义一个实现方法验证
// 每5s执行一次,使用自定义的线程池
    @Scheduled(cron = "*/5 * * * * ?")
    @Async("myThreadExecutor")
    public void test2(){
        System.out.println("myThreadExecutor test2");
    }

六、总结

线程池的工作机制原理:

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
  • 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
  • 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。

jdk中提供了 4 种拒绝策略实现:

  • AbortPolicy 让调用者抛出 RejectedExecutionException 异常,默认策略
  • CallerRunsPolicy 让调用者运行任务
  • DiscardPolicy 放弃本次任务
  • DiscardOldestPolicy 放弃队列中最早的任务,当前任务取而代之。

关于线程池更多知识请学习JUC中线程池相关知识。


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