springboot 使用线程池

@Configuration
@EnableAsync
public class ExecutePoolConfig {

    /**
     * 核心线程数
     */
    @Value("${taskThreadPool.corePoolSize}")
    private int corePoolSize;

    /**
     * 最大线程数
     */
    @Value("${taskThreadPool.maxPoolSize}")
    private int maxPoolSize;

    /**
     * 线程活跃时间
     */
    @Value("${taskThreadPool.keepAliveSeconds}")
    private int keepAliveSeconds;

    /**
     * 队列容量
     */
    @Value("${taskThreadPool.queueCapacity}")
    private int queueCapacity;

    /**
     * 线程池,如果有特殊的业务场景,可以自行再添加线程池
     * 使用示例   在需要使用线程池的方法上增加注解 @Async("taskExecutor")
     * @return
     */
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程池大小
        executor.setCorePoolSize(corePoolSize);
        //最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //队列容量
        executor.setQueueCapacity(queueCapacity);
        //活跃时间
        executor.setKeepAliveSeconds(keepAliveSeconds);
        //线程名字前缀
        executor.setThreadNamePrefix("Service-");

        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行,即变为单线程处理
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

  

1、线程池的优势

(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。


2、线程池的主要参数

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

5、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

描述:

    当向线程池提交一个任务时,若线程池已创建的线程数小于核心线程数,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于核心线程数时,会将新来的请求放到队列中等待执行,如果队列满了,则会创建新的线程来执行任务,如果达到的最大线程数,此时新来的请求,就会执行策略。当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数

3、线程池的4大拒绝策略

线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。

JDK 内置的拒绝策略如下:

1)AbortPolicy : 直接抛出 RejectedExecutionException 异常,阻止系统正常运行。

2)CallerRunsPolicy : 该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用线程。

3)DiscardOldestPolicy : 丢弃队列中等待最久的线程,然后把当前任务加入队列中尝试再次提交当前任务。

4)DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩展 RejectedExecutionHandler 接口。

4、线程池配置合理线程数

设置线程池的参数时,需要从以下 2 个方面进行考虑:

系统是 CPU 密集型?

系统是 IO 密集型?

(一)CPU 密集型

CPU 密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。

那么这种情况下,应该尽可能配置少的线程数量,从而减少线程之间的切换,让其充分利用时间进行计算。

一般公式为:CPU核数 + 1 个线程的线程池。

可以通过以下代码来查看服务器的核数:

Runtime.getRuntime().availableProcessors()
(二)IO 密集型

IO 密集型的意思是该任务需要大量的 IO,即大量的阻塞。

那么这种情况下会导致有大量的 CPU 算力浪费在等待上,所以需要多配置线程数。

在 IO 密集型情况下,了解到有两种配置线程数的公式:

公式一:CPU核数/(1-阻塞系数),其中阻塞系数在 0.8-0.9 之间

如:8核CPU,可以设置为 8/(1-0.9)=80 个线程

公式二:CPU核数 * 2

 
posted @ 2020-09-18 14:12  介寒食  阅读(1030)  评论(0编辑  收藏  举报