探秘SpringBoot默认线程池:了解其运行原理与工作方式(@Async和ThreadPoolTaskExecutor)
文章目录
- 文章导图
- Spring封装的几种线程池
- SpringBoot默认线程池
- TaskExecutionAutoConfiguration(SpringBoot 2.1后)
- 主要作用
- 优势
- 使用场景
- 如果没有它
- 2.1版本以后
- 如何查看参数
- 方式一:通过@Async注解--采用ThreadPoolTaskExecutor
- determineAsyncExecutor-查找决定使用哪个线程池
- 实操
- @Async注意点
- 方式二:直接注入 ThreadPoolTaskExecutor
- 2.1版本以前
- 方式一:通过@Async注解--采用SimpleAsyncTaskExecutor
- 方式二:直接注入 ThreadPoolTaskExecutor
- 直接注入报错
- 解决:自定义一个线程池
- 默认的线程池好用吗?
- TaskExecutionAutoConfiguration?
- SimpleAsyncTaskExecutor
- 线程池核心线程数和最大线程数设置指南
- 线程池参数介绍
- 线程数设置考虑因素
- CPU密集型任务的线程数设置
- IO密集型任务的线程数设置
- 实际应用中的线程数计算
- 生产环境中的线程数设置
- 线程池参数设置建议
- 注意事项
线程池系列文章可参考下表,目前已更新3篇,还剩1篇TODO…
线程池系列: | 文章 |
---|---|
Java基础线程池 | TODO… |
CompletableFuture线程池 | 从用法到源码再到应用场景:全方位了解CompletableFuture及其线程池 |
SpringBoot默认线程池(@Async和ThreadPoolTaskExecutor) | 探秘SpringBoot默认线程池:了解其运行原理与工作方式(@Async和ThreadPoolTaskExecutor) |
SpringBoot默认线程池和内置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说起
TaskExecutionAutoConfiguration(SpringBoot 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 ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers;private final ObjectProvider<TaskDecorator> taskDecorator;public TaskExecutionAutoConfiguration(TaskExecutionProperties properties,ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,ObjectProvider<TaskDecorator> 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,// 基于容器中存在的 TaskExecutorBuilder@Lazy// 使用bean名称 : taskExecutor, applicationTaskExecutor@Bean(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-prefix=task-
如何查看参数
在 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
为前缀。默认线程池的核心线程数coreSize=8
,最大线程数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")
就会去找程序中已有的定义好的name="taskExecutor"的Bean) -
如果没有指定限定符或找不到对应的Bean,它将尝试使用或创建一个默认的执行器。
- 如果是SpringBoot2.1以后,就会创建TaskExecutionAutoConfiguration帮我们自动配置好的默认线程池
ThreadPoolTaskExecutor
- 如果是SpringBoot2.1之前,TaskExecutionAutoConfiguration没有帮我们配置默认线程池,最终会找到
org.springframework.core.task.SimpleAsyncTaskExecutor
- 如果是SpringBoot2.1以后,就会创建TaskExecutionAutoConfiguration帮我们自动配置好的默认线程池
-
创建或确定执行器后,方法会将其存进缓存中,以备下次执行同一方法时使用。
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;@SneakyThrows@RequestMapping("/testSync")public void testTomcatThreadPool() {syncService.testAsync1();syncService.testAsync2();}
}
请求后观察控制台输出结果,打印线程名如下,说明我们这里采用的是TaskExecutionAutoConfiguration帮我们配置的默认线程池ThreadPoolTaskExecutor
,就是以task-
开头的
task-2
task-1
@Async注意点
-
启动类上需要添加
@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之前直接注入会报错@SneakyThrows@RequestMapping("/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;@SneakyThrows@RequestMapping("/testSync")public void testTomcatThreadPool() {syncService.testAsync1();syncService.testAsync2();}
}
验证输出结果,确实是采用了SimpleAsyncTaskExecutor
线程池:
SimpleAsyncTaskExecutor-1
SimpleAsyncTaskExecutor-2
方式二:直接注入 ThreadPoolTaskExecutor
最主要的就是此时我们没有TaskExecutionAutoConfiguration自动配置类,也就不会去配置默认的线程池,此时直接注入就会报错,所以需要自己定义!
直接注入报错
@RestController
public class TestController {@ResourceThreadPoolTaskExecutor taskExecutor; //SpringBoot2.1之前直接注入会报错@SneakyThrows@RequestMapping("/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核心数。
生产环境中的线程数设置
- 理论值与实际值可能存在差异,需要通过压力测试来确定最优线程数。
- 压力测试:调整线程数,观察系统性能,找到最优解。
线程池参数设置建议
- 核心业务应用:核心线程数设置为压力测试后的数值,最大线程数可以适当增加。
- 非核心业务应用:核心线程数设置较低,最大线程数设置为压力测试结果
注意事项
- 线程数设置需根据实际业务需求和系统环境进行调整。
- 持续监控和优化是保证系统性能的关键。
相关文章:

探秘SpringBoot默认线程池:了解其运行原理与工作方式(@Async和ThreadPoolTaskExecutor)
文章目录 文章导图Spring封装的几种线程池SpringBoot默认线程池TaskExecutionAutoConfiguration(SpringBoot 2.1后)主要作用优势使用场景如果没有它 2.1版本以后如何查看参数方式一:通过Async注解--采用ThreadPoolTaskExecutordetermineAsync…...

kubernetes(Jenkins、kubernetes核心、K8s实战-KubeSphere、)
文章目录 1. Jenkins1.1. 概述1.1.1. 简单部署1.1.2. 自动化部署1.1.3. DevOps概述1.1.4. CI/CD概述 1.2. jenkins介绍及安装1.2.1. 安装1.2.2. 解锁jenkins1.2.3. 安装推荐插件1.2.4. 创建管理员用户1.2.5. 升级jenkins版本1.2.6. 安装额外插件blue ocean1.2.7. jenkins界面说…...

国际数字影像产业园|科技与文创产品创意集市,共筑创新文化新高地
5月29日,为进一步增强园区与企业之间粘性,不断激发企业的创新活力,园区举办了“数媒大厦科技与文创产品创意集市活动”。本次活动由成都树莓信息技术有限公司主办,成都目莓商业管理有限公司、树莓科技(成都)…...

leetcode-55 跳跃游戏
leetcode Problem: 55. 跳跃游戏 思路 假设我们是一个小人,从第一个下标开始,每次经过一个位置,我们就可以根据当前位置的数值nums[i]和位置下标i计算出该位置所能到达的后续位置的最大值rnums[i]i。而这个r之前的区域一定都是可以经过的。…...

Vue——计算属性 computed 与方法 methods 区别探究
文章目录 前言计算属性的由来方法实现 计算属性 同样的效果计算属性缓存 vs 方法 前言 在官方文档中,给出了计算属性的说明与用途,也讲述了计算属性与方法的区别点。本篇博客只做自己的探究记录,以官方文档为准。 vue 计算属性 官方文档 …...

Java中的ORM框架——myBatis
一、什么是ORM ORM 的全称是 Object Relational Mapping。Object代表应用程序中的对象,Relational表示的是关系型数据库,Mapping即是映射。结合起来就是在程序中的对象和关系型数据库之间建立映射关系,这样就可以用面向对象的方式,…...

vue2生命周期和计算属性
vue2的生命周期 删除一些没用的 App.vue 删成这个样子就行 <template><router-view/></template><style lang"scss"></style>来到路由把没用的删除 import Vue from vue import VueRouter from vue-router import HomeView from .…...

Hadoop3:MapReduce之简介、WordCount案例源码阅读、简单功能开发
一、概念 MapReduce是一个 分布式运算程序 的编程框架,是用户开发“基于 Hadoop的数据分析 应用”的核心框架。 MapReduce核心功能是将 用户编写的业务逻辑代码 和 自带默认组件 整合成一个完整的 分布式运算程序 ,并发运行在一个 Hadoop集群上。 1、M…...

centos8stream 编译安装 php-rabbit-mq模块
官方GitHub:https://github.com/php-amqp/php-amqp 环境依赖安装 dnf install cmake make -y 1.安装rabbitmq-c cd /usr/local/src/ wget https://github.com/alanxz/rabbitmq-c/archive/refs/tags/v0.14.0.tar.gz tar xvf v0.14.0.tar.gz cd rabbitmq-c-0.14.0/…...

「异步魔法:Python数据库交互的革命」(二)
哈喽,我是阿佑,上篇文章带领了大家跨入的异步魔法的大门——Python数据库交互,一场魔法与技术的奇幻之旅! 从基础概念到DB-API,再到ORM的高级魔法,我们一步步揭开了数据库操作的神秘面纱。SQLAlchemy和Djan…...
php正则中的i,m,s,x,e分别表示什么
正则表达式模式修饰符(也称为标志或模式修饰符)用于改变正则表达式的行为。这些修饰符可以附加在正则表达式的定界符之后,通常为正斜杠(/)或井号(#),以改变搜索或替换的方式。 1、i…...

最新!2023年台湾10米DEM地形瓦片数据
上次更新谷歌倾斜摄影转换生成OSGB瓦片V1.1版本,使用该版本生产了台北、台中、桃园三个地方的倾斜摄影OSGB数据,在OSGB可视化软件中进行展示,可视化效果和加载效率俱佳。已经很久没更新地形瓦片数据,主要是热点地区的原始数据没有…...
网络学习(11) |深入解析客户端缓存与服务器缓存:HTTP缓存控制头字段及优化实践
文章目录 客户端缓存与服务器缓存的区别客户端缓存浏览器缓存应用程序缓存优点缺点 服务器缓存优点缺点 HTTP缓存控制头字段Cache-ControlExpiresLast-ModifiedETag 缓存策略的优化与实践经验分享1. 使用合适的缓存头字段2. 结合使用Last-Modified和ETag3. 利用CDN进行缓存4. 实…...
uniapp中二次封装jssdk和使用
直接上代码 // import wx from "weixin-js-sdk"; /*** 考虑到包的大小,所以直接在 index.html 文件中cdn引入了jssdk* <script src"https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>* 注意:这里 jWeixin 一…...

只刷题可以通过PMP考试吗?
咱们都知道,PMBOK那本书,哎呀,读起来确实有点费劲。所以,有些人就想了,干脆我就刷题吧,题海战术,没准儿也能过。这话啊,听起来似乎有点道理,但咱们得好好琢磨琢磨。 刷题…...

Python Selenium 详解:实现高效的UI自动化测试
落日余辉,深情不及久伴。大家好,在当今软件开发的世界中,自动化测试已经成为保障软件质量和快速迭代的重要环节。而在自动化测试的领域中,UI自动化测试是不可或缺的一部分,它可以帮助测试团队快速验证用户界面的正确性…...
npm获取yarn在安装依赖时 git://github.com/user/xx.git 无法访问解决方法 -- 使用 insteadOf设置git命令别名
今天在使用一个node项目时突然遇到 一个github的拉取异常,一看协议居然是git://xxx 貌似github早就不用这种格式了, 而是使用的gitgithub.com:xxx 这种或者https协议,解决方法: 使用insteadof设置git别名 url.<base>.inste…...

Centos7网络故障,开机之后连不上网ens33mtu 1500 qdisc noop state DOWN group default qlen 1000
说明 这是Linux系统网络接口的信息,其中"mtu 1500"表示最大传输单元大小为1500字节,“qdisc noop”表示没有设置特殊的队列算法,“state down”表示该接口当前处于关闭状态,“group default”表示该接口属于“default”…...
分析 Base64 编码和 URL 安全 Base64 编码
前言 在处理数据传输和存储时,Base64 编码是一种非常常见的技术。它可以将二进制数据转换为文本格式,便于在文本环境中传输和处理。Go 语言提供了对标准 Base64 编码和 URL 安全 Base64 编码的支持。本文将通过一个示例代码,来分析这两种编码…...
cocos 屏幕点击坐标转换为节点坐标
let scPos event.getLocation(); let camera find(Canvas/Camera).getComponent(Camera).screenToWorld(new Vec3(scPos.x,scPos.y,0));//摄像机 let p this.node.getComponent(UITransform).convertToNodeSpaceAR(camera);//this.node为指定的节点为原点(0,0&…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...