香橼做空机构网站,架设网站的目的,长沙 学校网站建设,网站 做英文 翻译 规则文章目录 文章导图Spring封装的几种线程池SpringBoot默认线程池TaskExecutionAutoConfiguration#xff08;SpringBoot 2.1后#xff09;主要作用优势使用场景如果没有它 2.1版本以后如何查看参数方式一#xff1a;通过Async注解--采用ThreadPoolTaskExecutordetermineAsync… 文章目录 文章导图Spring封装的几种线程池SpringBoot默认线程池TaskExecutionAutoConfigurationSpringBoot 2.1后主要作用优势使用场景如果没有它 2.1版本以后如何查看参数方式一通过Async注解--采用ThreadPoolTaskExecutordetermineAsyncExecutor-查找决定使用哪个线程池实操Async注意点 方式二直接注入 ThreadPoolTaskExecutor 2.1版本以前方式一通过Async注解--采用SimpleAsyncTaskExecutor方式二直接注入 ThreadPoolTaskExecutor直接注入报错解决自定义一个线程池 默认的线程池好用吗TaskExecutionAutoConfigurationSimpleAsyncTaskExecutor线程池核心线程数和最大线程数设置指南线程池参数介绍线程数设置考虑因素CPU密集型任务的线程数设置IO密集型任务的线程数设置实际应用中的线程数计算生产环境中的线程数设置线程池参数设置建议注意事项 线程池系列文章可参考下表目前已更新3篇还剩1篇TODO…
线程池系列文章Java基础线程池TODO…CompletableFuture线程池从用法到源码再到应用场景全方位了解CompletableFuture及其线程池SpringBoot默认线程池Async和ThreadPoolTaskExecutor探秘SpringBoot默认线程池了解其运行原理与工作方式Async和ThreadPoolTaskExecutorSpringBoot默认线程池和内置Tomcat线程池你是否傻傻分不清SpringBoot默认线程池和内置Tomcat线程池
文章导图 Spring封装的几种线程池
Spring框架为了简化并发任务的处理提供了几种线程池的封装实现这些线程池可以直接在Spring应用程序中使用。以下是Spring中常见的几种线程池
线程池类型描述特点SimpleAsyncTaskExecutor非阻塞的线程池为每个任务创建新线程不限制线程数量。不重用线程适合任务量不大的情况。SyncTaskExecutor同步执行器任务在调用线程中直接执行不创建新线程。无异步处理能力适用于简单同步操作。ConcurrentTaskExecutor包装类允许任何实现了java.util.concurrent.Executor的实现作为Spring的TaskExecutor。提供了对现有Executor实现的适配。ThreadPoolTaskExecutor功能丰富的线程池实现允许配置核心线程数、最大线程数、队列容量等。适合需要精细控制线程池参数的场合。WorkManagerTaskExecutor适用于Java EE环境下的CommonJ规范在WebLogic和WebSphere等应用服务器上有CommonJ 的 WorkManager支持时使用TaskExecutorAdapter适配器允许将实现了java.util.concurrent.ExecutorService的对象转换为Spring的TaskExecutor。为现有的ExecutorService提供Spring的TaskExecutor接口适配。
接下来我们重点看ThreadPoolTaskExecutor和SimpleAsyncTaskExecutor(Async)
SpringBoot默认线程池
SpringBoot默认的线程池就是ThreadPoolTaskExecutor但是在SpringBoot不同版本是有区别的下面先从这个关键的TaskExecutionAutoConfiguration说起
TaskExecutionAutoConfigurationSpringBoot 2.1后
/*** EnableAutoConfiguration Auto-configuration for TaskExecutor.** author Stephane Nicoll* author Camille Vienot* since 2.1.0*/// 仅在类 ThreadPoolTaskExecutor 存在于 classpath 时才应用
ConditionalOnClass(ThreadPoolTaskExecutor.class)
Configuration
// 确保前缀为 spring.task.execution 的属性配置项被加载到 bean TaskExecutionProperties 中
EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {/*** Bean name of the application TaskExecutor.*/public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME applicationTaskExecutor;private final TaskExecutionProperties properties;private final ObjectProviderTaskExecutorCustomizer taskExecutorCustomizers;private final ObjectProviderTaskDecorator taskDecorator;public TaskExecutionAutoConfiguration(TaskExecutionProperties properties,ObjectProviderTaskExecutorCustomizer taskExecutorCustomizers,ObjectProviderTaskDecorator taskDecorator) {this.properties properties;this.taskExecutorCustomizers taskExecutorCustomizers;this.taskDecorator taskDecorator;}// 定义 bean TaskExecutorBuilder taskExecutorBuilder// 这是一个 TaskExecutor 构建器Bean// 仅在该 bean 尚未被定义时才定义ConditionalOnMissingBeanpublic TaskExecutorBuilder taskExecutorBuilder() {TaskExecutionProperties.Pool pool this.properties.getPool();TaskExecutorBuilder builder new TaskExecutorBuilder();builder builder.queueCapacity(pool.getQueueCapacity());builder builder.corePoolSize(pool.getCoreSize());builder builder.maxPoolSize(pool.getMaxSize());builder builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());builder builder.keepAlive(pool.getKeepAlive());builder builder.threadNamePrefix(this.properties.getThreadNamePrefix());builder builder.customizers(this.taskExecutorCustomizers);builder builder.taskDecorator(this.taskDecorator.getIfUnique());return builder;}// 懒惰模式定义 bean ThreadPoolTaskExecutor applicationTaskExecutor// 基于容器中存在的 TaskExecutorBuilderLazy// 使用bean名称 : taskExecutor, applicationTaskExecutorBean(name { APPLICATION_TASK_EXECUTOR_BEAN_NAME,AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })// 仅在容器中不存在类型为 Executor 的 bean 时才定义 ConditionalOnMissingBean(Executor.class)public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {return builder.build();}}
注意上面的源码注释我们可以看到since 2.1.0所以TaskExecutionAutoConfiguration是SpringBoot2.1以后才有的那么这个类有啥作用呢
TaskExecutionAutoConfiguration 是 Spring Boot 提供的一个自动配置类它的目的是简化 Spring 应用中异步任务执行和任务调度的配置。
主要作用
自动配置异步任务执行器Executor TaskExecutionAutoConfiguration 会为应用配置一个默认的 ThreadPoolTaskExecutor这是一个基于线程池的 TaskExecutor 实现。它适用于应用中通过 Async 注解标注的异步方法的执行。如果你不提供自定义配置Spring Boot 将会使用这个自动配置的执行器来执行异步任务。自动配置任务调度器Scheduler 同样地它也为应用配置一个默认的 ThreadPoolTaskScheduler这是用于任务调度的组件支持 Scheduled 注解标注的方法。这个调度器允许你在应用中简便地安排定期执行的任务。可通过应用配置文件来定制配置 TaskExecutionAutoConfiguration 支持通过 application.properties 或 application.yml 配置文件来自定义任务执行和调度的相关参数例如线程池大小、队列容量等。这些参数分别位于 spring.task.execution 和 spring.task.scheduling 命名空间下。
优势
简化配置无需手动定义 Executor 或 Scheduler 的Bean大大减少了样板代码。易于定制通过配置文件即可调整线程池参数而无需修改代码。与 Spring 生态系统的集成自动配置的执行器和调度器与 Spring 的 Async 和 Scheduled 注解无缝集成。
使用场景
当你的应用需要执行异步任务或者按计划执行某些作业时你通常会需要配置一个任务执行器或者任务调度器。在 Spring Boot 应用中TaskExecutionAutoConfiguration 让这一步骤变得极为简单和自动化。
如果没有它
如果没有 TaskExecutionAutoConfiguration你需要手动配置线程池、异步执行器(TaskExecutor)和任务调度器(TaskScheduler)。即你需要在你的 Spring 配置中显式地定义相关的Bean并可能还要为这些Bean提供自定义的配置。
总结起来TaskExecutionAutoConfiguration 通过自动配置和简化配置工作大大提高了开发效率允许开发者更加专注于业务逻辑编写而无需担心底层线程池和执行器的配置。
2.1版本以后 2.1版本后有TaskExecutionAutoConfiguration自动配置类会帮我们自动配置默认线程池ThreadPoolTaskExecutor 线程池默认参数如下 核心线程数 (corePoolSize): 8 最大线程数 (maxPoolSize): Integer.MAX_VALUE (无限制) 队列容量 (queueCapacity): Integer.MAX_VALUE (无限制) 空闲线程保留时间 (keepAliveSeconds): 60秒 线程池拒绝策略 (RejectedExecutionHandler): AbortPolicy默认策略超出线程池容量和队列容量时抛出RejectedExecutionException异常
这些参数可以通过在application.properties或application.yml文件中设置来进行自定义调整。例如
# 核心线程数默认为8
spring.task.execution.pool.core-size
# 最大线程数默认为Integer.MAX_VALUE
spring.task.execution.pool.max-size
# 任务等待队列容量默认为Integer.MAX_VALUE
spring.task.execution.pool.queue-capacity
# 空闲线程等待时间默认为60s。如果超过这个时间没有任务调度则线程会被回收
spring.task.execution.pool.keep-alive
# 是否允许回收空闲的线程默认为true
spring.task.execution.pool.allow-core-thread-timeout
# 线程名前缀
spring.task.execution.thread-name-prefixtask-
如何查看参数
在 Spring Boot 中默认的线程池由 TaskExecutorBuilder 类负责创建它通常使用 ThreadPoolTaskExecutor 来配置默认的线程池。虽然默认的线程池参数可以根据不同的 Spring Boot 版本或特定配置而有所不同但通常情况下Spring Boot 默认的线程池参数如下
在org.springframework.boot.autoconfigure.task包的TaskExecutionAutoConfiguration.java是SpringBoot默认的任务执行自动配置类。 从EnableConfigurationProperties(TaskExecutionProperties.class)可以知道开启了属性绑定到TaskExecutionProperties.java的实体类上 打个断点可以看到默认参数如下
进入到TaskExecutionProperties.java类中看到属性绑定以spring.task.execution为前缀。默认线程池的核心线程数coreSize8最大线程数maxSize Integer.MAX_VALUE以及任务等待队列queueCapacity Integer.MAX_VALUE 因为Integer.MAX_VALUE的值为2147483647(2的31次方-1)所以默认情况下一般任务队列就可能把内存给堆满了。我们真正使用的时候还需要对异步任务的执行线程池做一些基础配置以防止出现内存溢出导致服务不可用的问题。 方式一通过Async注解–采用ThreadPoolTaskExecutor
determineAsyncExecutor-查找决定使用哪个线程池
我们的Async会默认使用什么线程池呢这里就涉及源码了核心源码如下org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor
这段方法的核心作用是根据Async注解和方法上是否有指定的限定符来决定一个方法应该使用哪个AsyncTaskExecutor来执行异步任务。 方法首先尝试从一个缓存(this.executors)中查找并返回已经与方法关联的执行器。 如果缓存里没有那么这个方法会尝试根据方法上的Async注解中指定的限定符从Spring应用程序上下文中查找对应的执行器。比如Async(taskExecutor)就会去找程序中已有的定义好的nametaskExecutor的Bean 如果没有指定限定符或找不到对应的Bean它将尝试使用或创建一个默认的执行器。 如果是SpringBoot2.1以后就会创建TaskExecutionAutoConfiguration帮我们自动配置好的默认线程池ThreadPoolTaskExecutor如果是SpringBoot2.1之前TaskExecutionAutoConfiguration没有帮我们配置默认线程池最终会找到org.springframework.core.task.SimpleAsyncTaskExecutor 创建或确定执行器后方法会将其存进缓存中以备下次执行同一方法时使用。
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {// 尝试从缓存中获取已确定的执行器AsyncTaskExecutor executor this.executors.get(method);if (executor null) {Executor targetExecutor; // 声明一个目标执行器String qualifier getExecutorQualifier(method); // 获取方法上的Async注解中指定的执行器的限定名// 如果注解中有限定名则尝试根据该名字从BeanFactory中查找对应的执行器if (StringUtils.hasLength(qualifier)) {targetExecutor findQualifiedExecutor(this.beanFactory, qualifier);}else {// 如果未指定限定名则使用默认的执行器targetExecutor this.defaultExecutor;// 如果默认执行器为空则尝试从BeanFactory中获取默认的执行器if (targetExecutor null) {synchronized (this.executors) {// 双重检查锁定确保只有一个线程可以初始化defaultExecutorif (this.defaultExecutor null) {this.defaultExecutor getDefaultExecutor(this.beanFactory);}targetExecutor this.defaultExecutor;}}}// 如果没有合适的目标执行器返回nullif (targetExecutor null) {return null;}// 如果目标执行器是AsyncListenableTaskExecutor则直接使用否则使用适配器封装以确保它实现了AsyncListenableTaskExecutor//所以SpringBoot2.1之前最终会找到org.springframework.core.task.SimpleAsyncTaskExecutorexecutor (targetExecutor instanceof AsyncListenableTaskExecutor ?(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));// 将确定的执行器放入缓存以便下次直接使用this.executors.put(method, executor);}// 返回确定的AsyncTaskExecutorreturn executor;
}
这个方法首先查找类型为 TaskExecutor 的Bean如果有多个此类型的Bean然后尝试查找名称为 “taskExecutor” 的Bean。如果这两种方式都没找到那么将返回 null表示没有找到适当的Bean来执行异步任务。这种情况下AsyncExecutionInterceptor 将不会有执行器来处理异步方法。
// 会去 Spring 的容器中找有没有 TaskExecutor 或名称为 taskExecutor 为 Executor 的 Nullableprotected Executor getDefaultExecutor(Nullable BeanFactory beanFactory) {if (beanFactory ! null) {try {return beanFactory.getBean(TaskExecutor.class);} catch (NoUniqueBeanDefinitionException ex) {return beanFactory.getBean(taskExecutor, Executor.class);} catch (NoSuchBeanDefinitionException ex) {return beanFactory.getBean(taskExecutor, Executor.class);}}return null;}实操
Service
public class SyncService {Asyncpublic void testAsync1() {System.out.println(Thread.currentThread().getName());ThreadUtil.sleep(10, TimeUnit.DAYS);}Asyncpublic void testAsync2() {System.out.println(Thread.currentThread().getName());ThreadUtil.sleep(10, TimeUnit.DAYS);}
}注意需要在启动类上需要添加EnableAsync注解否则不会生效。
RestController
public class TestController {AutowiredSyncService syncService;SneakyThrowsRequestMapping(/testSync)public void testTomcatThreadPool() {syncService.testAsync1();syncService.testAsync2();}
}请求后观察控制台输出结果打印线程名如下说明我们这里采用的是TaskExecutionAutoConfiguration帮我们配置的默认线程池ThreadPoolTaskExecutor就是以task-开头的
task-2
task-1Async注意点 启动类上需要添加EnableAsync注解 注解的方法必须是public方法。 注解的方法不要定义为static 方法一定要从另一个类中调用也就是从类的外部调用类的内部调用是无效的。
Spring 基于AOP 实现了异步因此需要获取到容器中的代理对象才能具有异步功能。假定原对象叫obj容器中的代理对象叫做 objproxy在执行 fun() 会同时执行aop带来的其他方法。但是如果在fun() 中调用了 fun2()那 fun2() 是原始的方法而不是经过aop代理后的方法就不会具有异步的功能。
方式二直接注入 ThreadPoolTaskExecutor 2.1版本以后我们有TaskExecutionAutoConfiguration自动配置类它会去配置默认的线程池此时注入的ThreadPoolTaskExecutor就是默认的线程池 RestController
public class TestController {ResourceThreadPoolTaskExecutor taskExecutor; //SpringBoot2.1之前直接注入会报错SneakyThrowsRequestMapping(/testSync)public void testTomcatThreadPool() {ThreadPoolExecutor threadPoolExecutor taskExecutor.getThreadPoolExecutor();}
}通过debug可以看到默认的线程池参数如下就是我们上面介绍的 2.1版本以前
方式一通过Async注解–采用SimpleAsyncTaskExecutor 最主要的就是此时我们没有TaskExecutionAutoConfiguration自动配置类也就不会去配置默认的线程池根据上面分析的determineAsyncExecutor方法此时Async会去采用默认的SimpleAsyncTaskExecutor Service
public class SyncService {Asyncpublic void testAsync1() {System.out.println(Thread.currentThread().getName());ThreadUtil.sleep(10, TimeUnit.DAYS);}Asyncpublic void testAsync2() {System.out.println(Thread.currentThread().getName());ThreadUtil.sleep(10, TimeUnit.DAYS);}
}注意需要再启动类上需要添加EnableAsync注解否则不会生效。
RestController
public class TestController {AutowiredSyncService syncService;SneakyThrowsRequestMapping(/testSync)public void testTomcatThreadPool() {syncService.testAsync1();syncService.testAsync2();}
}验证输出结果确实是采用了SimpleAsyncTaskExecutor线程池
SimpleAsyncTaskExecutor-1
SimpleAsyncTaskExecutor-2方式二直接注入 ThreadPoolTaskExecutor 最主要的就是此时我们没有TaskExecutionAutoConfiguration自动配置类也就不会去配置默认的线程池此时直接注入就会报错所以需要自己定义 直接注入报错
RestController
public class TestController {ResourceThreadPoolTaskExecutor taskExecutor; //SpringBoot2.1之前直接注入会报错SneakyThrowsRequestMapping(/testSync)public void testTomcatThreadPool() {ThreadPoolExecutor threadPoolExecutor taskExecutor.getThreadPoolExecutor();}
}此时发现在工程启动的时候就报错 解决自定义一个线程池
Configuration
public class ThreadPoolConfiguration {Bean(taskExecutor)public ThreadPoolTaskExecutor taskExecutor() {ThreadPoolTaskExecutor taskExecutor new ThreadPoolTaskExecutor();//设置线程池参数信息taskExecutor.setCorePoolSize(10);taskExecutor.setMaxPoolSize(50);taskExecutor.setQueueCapacity(200);taskExecutor.setKeepAliveSeconds(60);taskExecutor.setThreadNamePrefix(myExecutor--);taskExecutor.setWaitForTasksToCompleteOnShutdown(true);taskExecutor.setAwaitTerminationSeconds(60);//修改拒绝策略为使用当前线程执行taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//初始化线程池taskExecutor.initialize();return taskExecutor;}
}此时运行debug可以看到不会启动报错对应的线程池参数也是我们自定义的了 注意当我们上面自定义了线程池疑惑Sync对应的线程池也是我们自定义的这个不会再采用默认的SimpleAsyncTaskExecutor 默认的线程池好用吗
TaskExecutionAutoConfiguration
默认参数如下面所示 核心线程数 (corePoolSize): 8 最大线程数 (maxPoolSize): Integer.MAX_VALUE (无限制) 队列容量 (queueCapacity): Integer.MAX_VALUE (无限制) 空闲线程保留时间 (keepAliveSeconds): 60秒 线程池拒绝策略 (RejectedExecutionHandler): AbortPolicy默认策略超出线程池容量和队列容量时抛出RejectedExecutionException异常
那么可能会有什么问题
资源耗尽风险由于最大线程数设置为Integer.MAX_VALUE在高并发场景下如果任务持续涌入理论上可以创建无数线程这将迅速消耗系统资源如内存、CPU可能导致服务器崩溃或极端性能下降。实际上即使操作系统允许创建如此多的线程这种设置也是极度不可取的。响应性降低虽然核心线程数设置为8是比较合理的起始点但无限制的最大线程数意味着在高负载下线程创建不会受限这可能会影响到系统的响应时间和整体稳定性。过多的线程上下文切换会消耗大量CPU资源降低处理效率。队列无限增长队列容量也设置为Integer.MAX_VALUE这意味着当线程池中的线程都在忙碌且有新任务到来时这些任务将不断堆积在队列中。如果生产速率持续高于消费速率队列将无限增长最终可能导致OutOfMemoryError。异常处理策略过于简单粗暴采用AbortPolicy作为拒绝策略在线程池和队列都满载的情况下新的任务提交将直接抛出RejectedExecutionException异常而没有进行任何备份处理或降级策略这可能会导致部分业务操作失败且未被捕获处理的异常可能会影响到整个应用的稳定性。缺乏灵活性和控制对于不同的业务需求线程池参数应当根据具体情况进行调整。例如对响应时间敏感的服务可能需要更小的队列和更快的拒绝策略以避免任务长时间等待而对于可延迟处理的任务则可能需要更大的队列或不同的拒绝策略。
SimpleAsyncTaskExecutor
SimpleAsyncTaskExecutor 是Spring框架提供的一个简单的异步任务执行器它并不是一个真正的线程池实现。它的主要特点是每次调用 #execute(Runnable) 方法时都会创建一个新的线程来执行任务。这意味着它不会重用线程而是一次性的。
使用 SimpleAsyncTaskExecutor 可能会导致以下问题
线程资源浪费 每次执行任务时都创建新线程这会导致线程的生命周期短暂频繁地创建和销毁线程会消耗系统资源包括CPU时间和内存。系统资源耗尽 在高并发场景下如果大量任务同时提交SimpleAsyncTaskExecutor 可能会创建大量线程这可能会迅速耗尽系统资源如线程数限制和内存资源。缺乏线程管理 真正的线程池通常包含线程管理机制如线程重用、线程池大小控制、任务队列管理等。SimpleAsyncTaskExecutor 缺乏这些特性因此在面对大量并发任务时可能无法有效地管理资源。没有任务队列 由于 SimpleAsyncTaskExecutor 不使用任务队列它无法有效地缓冲任务。当任务提交速度超过执行速度时新提交的任务可能会因为没有可用的线程而立即失败。不适合长期运行的服务 对于需要长期运行的服务使用 SimpleAsyncTaskExecutor 可能会导致系统不稳定因为它没有为长期运行的任务提供稳定的线程资源。
总结 SimpleAsyncTaskExecutor可以简单地用于异步任务执行但由于它的设计限制一般不推荐在生产环境中使用特别是在要求高并发或资源使用高效的场景下。在这些情况下更推荐使用可配置、可复用线程的线程池如ThreadPoolTaskExecutor因为它提供了更多的灵活性、效率和控制能力。
线程池核心线程数和最大线程数设置指南
线程池参数介绍
核心线程数线程池中始终活跃的线程数量。最大线程数线程池能够容纳同时执行的最大线程数量。
线程数设置考虑因素
CPU密集型任务依赖CPU计算的任务如循环计算等。IO密集型任务任务执行过程中涉及等待外部操作如数据库读写、网络请求、磁盘读写等。
CPU密集型任务的线程数设置
推荐设置核心数 1。 原因避免线程切换开销同时允许一定程度的线程中断恢复。
IO密集型任务的线程数设置
推荐设置2 * CPU核心数。 原因IO操作期间CPU可执行其他线程任务提高资源利用率。
实际应用中的线程数计算
使用工具如Java的Visual VM来监测线程的等待时间和运行时间。计算公式线程等待时间 / 线程总运行时间 1 * CPU核心数。
生产环境中的线程数设置
理论值与实际值可能存在差异需要通过压力测试来确定最优线程数。压力测试调整线程数观察系统性能找到最优解。
线程池参数设置建议
核心业务应用核心线程数设置为压力测试后的数值最大线程数可以适当增加。非核心业务应用核心线程数设置较低最大线程数设置为压力测试结果
注意事项
线程数设置需根据实际业务需求和系统环境进行调整。持续监控和优化是保证系统性能的关键。