美文网首页
SpringBoot 整合线程池及各参数详解

SpringBoot 整合线程池及各参数详解

作者: 大程子的技术成长路 | 来源:发表于2021-02-09 14:58 被阅读0次

代码结构自上而下

ThreadPoolConfig

package com.studymongo.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
* 线程池配置类
* @author wangcp
* @date 2021/02/09 11:19
**/
@Data
@EnableAsync
@Configuration
@ConfigurationProperties(prefix = "poolconfig")
public class ThreadPoolConfig {

    /**
     * 线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
     */
    private int corePoolSize;

    /**
     * 线程池维护线程的最大数量
     */
    private int maxPoolSize;

    /**
     * 缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
     */
    private int queueCapacity;

    /**
     * 允许的空闲时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
     */
    private int keepAlive;


    @Bean
    public TaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(corePoolSize);
        // 设置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        // 设置队列容量
        executor.setQueueCapacity(queueCapacity);
        // 设置允许的空闲时间(秒)
        executor.setKeepAliveSeconds(keepAlive);
        // 设置默认线程名称
        executor.setThreadNamePrefix("thread-");
        // 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALL_RUNS:不在新线程中执行任务,二十由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后在关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

ThreadController

package com.studymongo.controller;

import com.studymongo.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 线程测试控制类
* @author wangcp
* @date 2021/02/09 11:32
**/
@RestController
public class ThreadController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping(value = "/thread")
    public void thread(){
        asyncService.executeAsync();
    }
}

AsyncService

package com.studymongo.service;

public interface AsyncService {
    void executeAsync();
}

AsyncServiceImpl

package com.studymongo.service.impl;

import com.studymongo.service.AsyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
* 实现类
* @author wangcp
* @date 2021/02/09 11:35
**/
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {

    @Async("taskExecutor")
    @Override
    public void executeAsync() {
        log.info("start executeAsync");
        try {
            System.out.println("当前运行线的程名称:" + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("end executeAsync");
    }
    
}

application.yaml

poolconfig:
  corePoolSize: 3
  maxPoolSize: 6
  queueCapacity: 10
  keepAlive: 100

运行结果

image-20210209140558610.png
根据运行结果发现,线程运行时有序的!

线程池的各个参数详解

参数默认值及含义

# 线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
private int corePoolSize = 1;

# 线程池维护线程的最大数量
private int maxPoolSize = 2147483647;

# 允许的空闲时间,当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
private int keepAliveSeconds = 60;

# 缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
private int queueCapacity = 2147483647;

#拒绝策略
rejectedExecutionHandler = AbortPolicy()

对照源码解析,可以得出以下结论:

1.如果线程池中线程数量 < 核心线程数,新建一个线程执行任务;

2.如果线程池中线程数量 >= 核心线程数,则将任务放入任务队列

3.如果线程池中线程数量 >= 核心线程数 且 < maxPoolSize,且任务队列满了,则创建新的线程;

4.如果线程池中线程数量 > 核心线程数,当线程空闲时间超过了keepalive时,则会销毁线程;由此可见线程池的队列如果是无界队列,那么设置线程池最大数量是无效的;

5.如果线程池中的任务队列满了,而且线程数达到了maxPoolSize,并且没有空闲的线程可以执行新的任务,这时候再提交任务就会执行拒绝策略

拒绝策略详解

拒绝策略RejectedExecutionHandler分为以下5种

image-20210209142923446.png
  • AbortPolicy

    该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常

  • CallerRunsPolicy

    使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。就像是个急脾气的人,我等不到别人来做这件事就干脆自己干。

  • DiscardOldestPolicy

    这个策略从字面上也很好理解,丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
    因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队。

  • DiscardPolicy

    这个策略和AbortPolicy的slient版本,如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。

  • 自定义

    如果以上策略都不符合业务场景,那么可以自己定义一个拒绝策略,只要实现RejectedExecutionHandler接口,并且实现rejectedExecution方法就可以了。具体的逻辑就在rejectedExecution方法里去定义就OK了。

    public class MyRejectPolicy implements RejectedExecutionHandler{
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            //Sender是我的Runnable类,里面有message字段
            if (r instanceof Sender) {
                Sender sender = (Sender) r;
                //直接打印
                System.out.println(sender.getMessage());
            }
        }
    }
    

结论

这几种策略没有好坏之分,只是适用不同场景,具体哪种合适根据具体场景和业务需要选择,如果需要特殊处理就自己定义好了。

相关文章

网友评论

      本文标题:SpringBoot 整合线程池及各参数详解

      本文链接:https://www.haomeiwen.com/subject/vrqcxltx.html