前言:
1、实际项目开发中有大量的线程的调用,不断的创建与销毁线程会产生很多不必要的开销,因此在实际开发中,咱们往往会使用线程池来对线程进行管理。然而,很多人对线程池的配置及配置原因并不是那么清楚,今天这篇文章就是简单的对线程池的各个参数及一些注意事项做个分析
线程池的优点:
1、降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
2、提高响应速度。不需要等待线程创建
3、提高线程的可管理性,对线程进行统一分配、调优和监控。
线程池的一些概念:
1、线程池可分为IO密集型、CPU密集型、以及混合型,大部分时候需要针对系统来设定合理的线程池线程的大小。
2、线程的核心线程数与最大线程数经常见到设置的非常大,然而线程也叫轻量级进程,他的调度需要系统分配时间片然后调用CPU执行逻辑,但是CPU是有限的,几个例子:服务器有8个CPU,你有1000个线程,但是执行的依然还是是这8个CPU,同一时间最多也是8个CPU,因此过多的创建线程是没有正向意义的,只是增加了内存的开销。
3、IO密集型,顾名思义,系统存在大量的IO,线程获得时间片后大量的时间在等待IO响应,此时的CPU其实是可以处理其他的计算的,因此IO密集型的线程数往往可以设置的稍微大一些,例如:2*N(cpu)。
4、CPU密集型,顾名思义,系统存在大量的CPU计算,CPU的利用率非常高,此时CPU的等待时间很短,因此线程往往不需要有太多,例如:N(cpu)+1。
5、混合型,顾名思义,混子,二者都有,数量小于IO密集型大于CPU密集型即可,或者使用两个线程池,一个处理IO密集型的任务,一个处理CPU密集型的任务。
6、线程池队列:建议使用有界队列(有长度限制的),有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点,比如几千。
7、线程池的饱和策略,包括:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 默认策略
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池的处理流程:
当调用线程池时首先判断线程是否空闲,空闲则直接使用该线程执行逻辑,否则创建核心线程再执行逻辑。
当核心线程数满后,将新的任务放到工作队列中,遵循先进先出的原则。
当工作队列满后,将创建新的线程执行任务,直到达到最大线程数。
当最大线程数,工作队列都达到上限后,采用开始指定的饱和策略。
1、核心线程->工作队列->最大线程->饱和策略
2、当工作队列满后才会继续创建最大线程
3、饱和策略是在队列和线程都满了之后的策略
4、程序运行饱和策略说明工作队列或最大线程等参数设置不合理或是程序死锁造成卡死,往往需要对逻辑进行改善
线程池的运行:
1、线程池在创建线程时需要获取全局锁,性能消耗较大,因此在核心线程满足后将任务加入队列,尽可能的避免获取全局锁,造成多余的性能开销。
2、当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使有空闲线程能执行新任务也会创建线程,等到需要执行的任务数大于线程池大小就不再创建
3、新创建的线程完成任务后会反复从队列中获取任务来执行。
线程池的关闭:
1、原理是遍历所有工作线程,逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止
线程池配置,示例(采用的Spring的线程池):
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.concurrent.ThreadPoolExecutor;
/**
* ScheduleConfig
* <p/>
* 线程池配置
* @author kmchen
* @date 2020/6/12 11:38
* Copyright (c) 2020 MANJDD
*/
@Configuration
@Component
public class ScheduleConfig {
/**
* 实例化线程池,由于业务是CPU密集型的,比较耗费CPU,
* 生产环境机器是2核的,处理太多线程会照成线程切换耗费时间
* 所以此处减少线程数量,增大队列容量
*
* @return org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
**/
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
// 获取当前机器CPU核数
int cpuProcessors = Runtime.getRuntime().availableProcessors();
if (cpuProcessors == 0) {
cpuProcessors = 4;
}
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(cpuProcessors);
executor.setMaxPoolSize(cpuProcessors+1);
// 线程池维护线程所允许的空闲时间
executor.setKeepAliveSeconds(60);
// 队列长度
executor.setQueueCapacity(1000);
// 等待任务执行完成在关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
//该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
executor.setAwaitTerminationSeconds(60);
// 线程前缀名称
executor.setThreadNamePrefix("async-service-");
// 配置拒绝策略:如果队列满了,继续往队列增加数据,则直接丢弃
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
executor.initialize();
return executor;
}
}