Spring Boot 配置线程池详解

线程池是多线程的处理机制,线程池一般用于需要大量线程完成任务,并且完成时间较短时使用,大量用于并发框架和异步执行任务。

一、配置线程池的优势

  • 降低资源消耗,通过利用已创建的线程降低线程创建和销毁造成的消耗
  • 有利于线程的可控性,如果线程无休止创建,会导致内存耗尽。
  • 提高系统响应速度,通过使用已存在的线程,不需要等待新线程的创建就可以立即执行当前任务。

二、spring中线程池ThreadPoolTaskExecutor的主要参数说明。

  • corePoolSize:核心线程数,默认的核心线程的1,向线程池提交一个任务时,如果线程池已经创建的线程数小于核心线程数,即使此时存在空闲线程,也会通过创建一个新线程来执行新任务,知道创建的线程等于核心线程数时,如果有空闲线程,则使用空闲线程。
  • maxPoolSize:最大线程数,默认的最大线程数为Integer.MAX_VALUE 即231-1。当队列满了之后
  • keepAliveSeconds:允许线程空闲时间,默认的线程空闲时间为60秒,当线程中的线程数大于核心线程数时,线程的空闲时间如果超过线程的存活时间,则此线程会被销毁,直到线程池中的线程数小于等于核心线程数时。
  • queueCapacity:缓冲队列数,默认的缓冲队列数是Integer.MAX_VALUE 即231-1,用于保存执行任务的阻塞队列
  • allowCoreThreadTimeOut:销毁机制,allowCoreThreadTimeOut为true则线程池数量最后销毁到0个。allowCoreThreadTimeOut为false销毁机制:超过核心线程数时,而且(超过最大值或者timeout过),就会销毁。默认是false

三、Spring Boot 中创建线程池ThreadPoolTaskExecutor

/**
 * @author yly
 * @ClassName ThreadPoolConfig
 * @Date 2020/2/26 12:10
 * @Version 1.0
 **/
@Configuration
public class ThreadPoolConfig {

        /**
     * 核心线程数
     * 默认的核心线程数为1
     *
     */
    private static final int CORE_POOL_SIZE = 5;
    /**
     * 最大线程数
     * 默认的最大线程数是Integer.MAX_VALUE 即2<sup>31</sup>-1
     */
    private static final int MAX_POOL_SIZE = 50;
    /**
     * 缓冲队列数
     * 默认的缓冲队列数是Integer.MAX_VALUE 即2<sup>31</sup>-1
     */
    private static final int QUEUE_CAPACITY = 100;

    /**
     * 允许线程空闲时间
     * 默认的线程空闲时间为60秒
     */
    private static final int KEEP_ALIVE_SECONDS = 30;

    /**
     * 线程池前缀名
     */
    private static final String THREAD_NAME_PREFIX = "Task_Service_Async_";

    /**
     * allowCoreThreadTimeOut为true则线程池数量最后销毁到0个
     * allowCoreThreadTimeOut为false
     * 销毁机制:超过核心线程数时,而且(超过最大值或者timeout过),就会销毁。
     * 默认是false
     */
    private boolean allowCoreThreadTimeOut = false;

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
        taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        taskExecutor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //线程池初始化
        taskExecutor.initialize();
        return taskExecutor;
    }
 }

其中setRejectedExecutionHandler()设置拒绝执行处理程序。

/**
	 * Set the RejectedExecutionHandler to use for the ExecutorService.
	 * Default is the ExecutorService's default abort policy.
	 * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy
	 */
	public void setRejectedExecutionHandler(@Nullable RejectedExecutionHandler rejectedExecutionHandler) {
		this.rejectedExecutionHandler =
				(rejectedExecutionHandler != null ? rejectedExecutionHandler : new ThreadPoolExecutor.AbortPolicy());
	}

ThreadPoolTaskExecutor 的拒绝执行处理程序共有四种,在ThreadPoolExecutor类中

spring boot线程池配置 springboot自带线程池_多线程

第一种处理程序CallerRunsPolicy

/**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {

被拒绝任务的处理程序,它直接在{@code execute}方法的调用线程中运行被拒绝的任务,除非执行程序已经关闭,在这种情况下任务被丢弃。

第二种处理程序AbortPolicy

/**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {

抛出{@code RejectedExecutionException}的被拒绝任务的处理程序。

第三种处理程序DiscardOldestPolicy

/**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {

被拒绝任务的处理程序,它丢弃最古老的未处理请求,然后重试{@code execute},除非执行程序关闭,在这种情况下任务被丢弃。

第四种处理程序DiscardPolicy

/**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {

一个处理被拒绝任务的处理程序,它无声地丢弃被拒绝的任务。

四、Spring Boot 中创建线程池ThreadPoolTaskScheduler

/**
 * @author yly
 * @ClassName ThreadPoolConfig
 * @Date 2020/2/26 12:10
 * @Version 1.0
 **/
@Configuration
public class ThreadPoolConfig {

    /**
     * 设置调度的执行程序服务的池大小。
     */
    private int poolSize = 4;
    /**
     * 设置任务注册器的调度器
     * 在{@link ScheduledThreadPoolExecutor}上设置取消操作模式。默认是{@code false}。
     * 如果设置为{@code true},则目标执行程序将切换到“删除-取消”模式(如果可能,则使用软回退)。
     * 这个设置可以在运行时修改,例如通过JMX。
     *
     */
    private boolean removeOnCancelPolicy = true;
    /**
     * 线程池前缀名
     */
    private String threadNamePrefix = "TaskSchedulerThreadPool-";

    @Bean("taskScheduler")
    public ThreadPoolTaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

        scheduler.setPoolSize(poolSize);

        scheduler.setRemoveOnCancelPolicy(removeOnCancelPolicy);

        scheduler.setThreadNamePrefix(threadNamePrefix);
        return scheduler;
    }
}

ThreadPoolTaskScheduler参数说明

  • poolSize :设置调度的执行程序服务的池大小。
  • removeOnCancelPolicy : * 设置任务注册器的调度器在{@link ScheduledThreadPoolExecutor}上设置取消操作模式。默认是{@code false}。如果设置为{@code true},则目标执行程序将切换到“删除-取消”模式(如果可能,则使用软回退)。这个设置可以在运行时修改。
  • threadNamePrefix :线程池前缀名

其中setErrorHandler()方法是自定义错误处理程序策略

五、 线程池的创建流程

创建线程池时,首先判断线程池的核心线程池是否已满,没满则创建新的工作线程执行任务,否则判断任务队列是否已满,没满则将新提交的任务添加到工作队列,否则判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,否执行饱和策略。

六、线程池参数推荐设置

  • corePoolSize:核心线程数(taskstasktime个线程数),假设系统每秒任务数为100到1000,每个任务耗时0.1秒,则需要1000.1至1000*0.1,即10到100个线程
  • maxPoolSize:最大线程数,每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。
  • queueCapacity:缓冲队列数,队列长度可以设置为(corePoolSize/tasktime)responsetime: (20/0.1)2=400,即队列长度可设置为400。*

七、使用@Async,异步方法执行

(1)创建SpringBoot web项目

<?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 https://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.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yly</groupId>
    <artifactId>async</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>async</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-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

(2)在启动类上添加@EnableAsync和@EnableScheduling(为了查看使用线程池配置定时任务)

/**
 * @author 81509
 */
@SpringBootApplication
@EnableAsync
@EnableScheduling
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

}

(3)创建SendSmsService

/**
 * @author yly
 * @ClassName SendSmsService
 * @Date 2020/2/26 11:31
 * @Version 1.0
 **/
@Service
@Async("taskExecutor")//可以加在类上或方法上
public class SendSmsService {

    Logger logger = LoggerFactory.getLogger(getClass());

    public void sendMerchant() {
        logger.info("通知商铺--------开始");
        try {
            logger.info("sleep----3秒");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("通知商铺--------完成");
    }

    public void sendConsumer()  {
        logger.info("通知消费者---------开始");
        try {
            logger.info("sleep----1秒");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("通知消费者---------完成");
    }
}

(4)配置线程池

/**
 * @author yly
 * @ClassName ThreadPoolConfig
 * @Date 2020/2/26 12:10
 * @Version 1.0
 **/
@Configuration
public class ThreadPoolConfig {

        /**
     * 核心线程数
     * 默认的核心线程数为1
     *
     */
    private static final int CORE_POOL_SIZE = 5;
    /**
     * 最大线程数
     * 默认的最大线程数是Integer.MAX_VALUE 即2<sup>31</sup>-1
     */
    private static final int MAX_POOL_SIZE = 50;
    /**
     * 缓冲队列数
     * 默认的缓冲队列数是Integer.MAX_VALUE 即2<sup>31</sup>-1
     */
    private static final int QUEUE_CAPACITY = 100;

    /**
     * 允许线程空闲时间
     * 默认的线程空闲时间为60秒
     */
    private static final int KEEP_ALIVE_SECONDS = 30;
    
    /**
     * 线程池前缀名
     */
    private static final String THREAD_NAME_PREFIX = "Task_Service_Async_";

    /**
     * allowCoreThreadTimeOut为true则线程池数量最后销毁到0个
     * allowCoreThreadTimeOut为false
     * 销毁机制:超过核心线程数时,而且(超过最大值或者timeout过),就会销毁。
     * 默认是false
     */
    private boolean allowCoreThreadTimeOut = false;

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
        taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        taskExecutor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}

(5)创建demo类调用异步方法,需要加上@Component加入到容器中

/**
 * @author yly
 * @ClassName demo
 * @Date 2020/2/26 11:37
 * @Version 1.0
 **/
@Component
public class demo {

    @Autowired
    private SendSmsService sendSmsService;

    @Scheduled(fixedRate  = 1000)//定时任务,一秒执行一次
    public void test()  {
        sendSmsService.sendMerchant();
        sendSmsService.sendConsumer();
    }
}

(6)执行结果

spring boot线程池配置 springboot自带线程池_处理程序_02