如何配置springboot线程池
01-springboot内部有几类线程
我个人了解到springboot里面一般只有两种类型的线程,如果我们要自定义线程池的话,那么就应该有三种
1,springboot内置Tomcat线程池
2,GC线程,GC线程归JVM管
3,自定义线程池,我们自己显式创建的线程
02-springboot线程池配置标准
基于上述因素,可以按照以下的一般性建议进行配置:
1,线程池大小的计算:线程池大小一般建议设置为 2 * CPU核数 + 1。
2,JVM内存的计算:JVM内存的大小一般建议设置为可用内存的 1/4 到 1/2 左右,具体大小取决于应用程序的需要和系统可用内存。
3, 根据实际情况进行调整:以上建议只是一个一般性的参考,具体的配置还需要根据应用程序的类型和特性以及实际运行情况进行调整。可以通过监控工具来观察应用程序的运行情况,以便进行优化和调整。
其中需要注意:
一个问题:
线程池大小的计算:线程池大小一般建议设置为 2 * CPU核数 + 1,这个是指线程池最大线程数量吗?还是线程池的最小核心线程数量?
答案:
线程池大小一般建议设置为 2 * CPU核数 + 1 是指线程池的最大线程数量。该公式是一个经验公式,旨在帮助计算出适当的线程池大小,以提高应用程序的性能。具体线程池大小的设置还需要考虑实际的业务场景,包括处理的任务类型、任务数量、任务处理时间等因素。另外,线程池的最小核心线程数量一般设置为1,表示即使没有任务需要处理,线程池中也至少有一个线程处于运行状态,以保证线程池的可用性。
03-springboot Tomcat线程池配置:
1,注意我们请求到springboot的Controller就会用到Tomcat线程池
server:
port: 8080 # 应用程序监听的端口号
servlet:
context-path: / # 应用程序的根路径
tomcat:
max-threads: 200 # tomcat线程池的最大线程数
min-spare-threads: 20 # tomcat线程池的最小空闲线程数
max-http-header-size: 1MB # 最大HTTP头大小
accesslog:
enabled: true # 是否启用Tomcat的访问日志
directory: ./logs # 日志文件的存储路径
prefix: access_log # 日志文件名的前缀
suffix: .log # 日志文件名的后缀
rotate: true # 是否启用日志文件轮转
rename-on-rotate: true # 是否在轮转时重命名旧日志文件
file-date-format: .yyyy-MM-dd # 日志文件名中的日期格式
Controller请求会发生什么事情:
简单描述, 因为不是正文内容:
假设你发一个请求到Spring Boot应用程序中的Controller接口,以下是Spring Boot应用程序在处理该请求时涉及到的一些关键类和方法:
- Spring Boot应用程序的入口类:通常情况下,Spring Boot应用程序的入口类会打上
@SpringBootApplication
注解,该注解包含了@ComponentScan
和@EnableAutoConfiguration
注解,分别用于扫描Spring Bean并自动配置Spring Boot应用程序的一些特性。 - DispatcherServlet:DispatcherServlet是Spring MVC框架的核心组件之一,负责接收所有的HTTP请求并将其路由到适当的Controller方法中进行处理。
- Controller类:Controller类是你应用程序中的一部分,通常使用
@RestController
或@Controller
注解进行标注,用于处理具体的HTTP请求。 - Service类:Service类是Spring中用于处理业务逻辑的组件,可以使用
@Service
注解进行标注。 - Repository类:Repository类是Spring Data框架中用于访问数据库的组件,可以使用
@Repository
注解进行标注。 - Tomcat线程池:Spring Boot内置了Tomcat作为默认的Web服务器,其中的线程池默认使用
SimpleAsyncTaskExecutor
来处理异步请求。当请求到达应用程序时,Tomcat会为该请求分配一个线程,线程会调用Spring MVC框架的DispatcherServlet进行请求分发和处理。在处理过程中,如果有必要执行异步操作,例如发送电子邮件或执行长时间运行的任务,线程池将为该任务分配一个额外的线程,该线程将在后台处理该任务。 - 与请求创建和消亡相关的方法:Spring MVC框架中有一些方法用于处理HTTP请求的创建和消亡。其中,
HandlerMapping
接口用于映射请求URL到对应的Controller方法,HandlerAdapter
接口用于调用Controller方法并将其结果转换为HTTP响应。在响应被发送到客户端之前,还有一些其他的拦截器和处理器可以对其进行修改。
需要注意的是,当处理请求的线程执行完毕时,并不会立即被GC回收,而是会被Tomcat线程池中的线程管理器重新加入线程池,等待下一次请求的到来。因此,即使线程执行完毕,该线程仍然由Tomcat线程池管理,而不是由Java虚拟机的垃圾回收器管理。
04-自定义线程池配置:
yml配置:
#线程池配置
task:
pool:
corePoolSize: 5
maxPoolSize: 7
keepAliveSeconds: 300
queueCapacity: 30
自定义线程池配置
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 重写默认线程池配置
* @author 太初
*/
@Slf4j
@Configuration
@EnableAsync
//@EnableScheduling
public class OverrideDefaultThreadPoolConfig implements AsyncConfigurer {
@Autowired
private TaskThreadPoolConfig config;
@Bean("asyncTaskExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程池大小
executor.setCorePoolSize(config.getCorePoolSize());
//最大线程数
executor.setMaxPoolSize(config.getMaxPoolSize());
//队列容量
executor.setQueueCapacity(config.getQueueCapacity());
//活跃时间
executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
//线程名字前缀
executor.setThreadNamePrefix("fmetro-thread-");
/*
当poolSize已达到maxPoolSize,如何处理新任务(是拒绝还是交由其它线程处理)
CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
CallerRunsPolicy:使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。就像是个急脾气的人,我等不到别人来做这件事就干脆自己干。
AbortPolicy:该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
DiscardPolicy:这个策略和AbortPolicy的slient版本,如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。
DiscardOldestPolicy:丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
/**
* 异步任务中异常处理
*
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("==========================" + ex.getMessage() + "=======================", ex);
log.error("exception method:" + method.getName());
};
}
}
使用自定义线程:
@Async("asyncTaskExecutor")