探秘SpringBoot默认线程池:了解其运行原理与工作方式(@Async和ThreadPoolTaskExecutor)

news2024/11/17 22:15:30

文章目录

  • 文章导图
  • Spring封装的几种线程池
  • SpringBoot默认线程池
    • TaskExecutionAutoConfiguration(SpringBoot 2.1后)
      • 主要作用
      • 优势
      • 使用场景
      • 如果没有它
    • 2.1版本以后
      • 如何查看参数
      • 方式一:通过@Async注解--采用ThreadPoolTaskExecutor
        • determineAsyncExecutor-查找决定使用哪个线程池
        • 实操
        • @Async注意点
      • 方式二:直接注入 ThreadPoolTaskExecutor
    • 2.1版本以前
      • 方式一:通过@Async注解--采用SimpleAsyncTaskExecutor
      • 方式二:直接注入 ThreadPoolTaskExecutor
        • 直接注入报错
        • 解决:自定义一个线程池
    • 默认的线程池好用吗?
      • TaskExecutionAutoConfiguration?
      • SimpleAsyncTaskExecutor
      • 线程池核心线程数和最大线程数设置指南
        • 线程池参数介绍
        • 线程数设置考虑因素
        • CPU密集型任务的线程数设置
        • IO密集型任务的线程数设置
        • 实际应用中的线程数计算
        • 生产环境中的线程数设置
        • 线程池参数设置建议
        • 注意事项

线程池系列文章可参考下表,目前已更新2篇,还剩两篇TODO…

线程池系列:文章
Java基础线程池TODO…
CompletableFuture线程池TODO…
SpringBoot默认线程池(@Async和ThreadPoolTaskExecutor)探秘SpringBoot默认线程池:了解其运行原理与工作方式(@Async和ThreadPoolTaskExecutor)
SpringBoot默认线程池和内置Tomcat线程池你是否傻傻分不清SpringBoot默认线程池和内置Tomcat线程池?

文章导图

image-20240522213822974

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接口适配。

接下来我们重点看ThreadPoolTaskExecutorSimpleAsyncTaskExecutor(@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 尚未被定义时才定义
	@ConditionalOnMissingBean
	public 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 应用中异步任务执行和任务调度的配置。

主要作用

  1. 自动配置异步任务执行器(Executor): TaskExecutionAutoConfiguration 会为应用配置一个默认的 ThreadPoolTaskExecutor,这是一个基于线程池的 TaskExecutor 实现。它适用于应用中通过 @Async 注解标注的异步方法的执行。如果你不提供自定义配置,Spring Boot 将会使用这个自动配置的执行器来执行异步任务。
  2. 自动配置任务调度器(Scheduler): 同样地,它也为应用配置一个默认的 ThreadPoolTaskScheduler,这是用于任务调度的组件,支持 @Scheduled 注解标注的方法。这个调度器允许你在应用中简便地安排定期执行的任务。
  3. 可通过应用配置文件来定制配置: TaskExecutionAutoConfiguration 支持通过 application.propertiesapplication.yml 配置文件来自定义任务执行和调度的相关参数,例如线程池大小、队列容量等。这些参数分别位于 spring.task.executionspring.task.scheduling 命名空间下。

优势

  • 简化配置:无需手动定义 ExecutorScheduler 的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.propertiesapplication.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的实体类上
img
打个断点可以看到默认参数如下:
在这里插入图片描述

进入到TaskExecutionProperties.java类中,看到属性绑定以spring.task.execution为前缀。默认线程池的核心线程数coreSize=8,最大线程数maxSize = Integer.MAX_VALUE,以及任务等待队列queueCapacity = Integer.MAX_VALUE
因为Integer.MAX_VALUE的值为2147483647(2的31次方-1),所以默认情况下,一般任务队列就可能把内存给堆满了。我们真正使用的时候,还需要对异步任务的执行线程池做一些基础配置,以防止出现内存溢出导致服务不可用的问题。

img

方式一:通过@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
  • 创建或确定执行器后,方法会将其存进缓存中,以备下次执行同一方法时使用。

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) {
                    // 双重检查锁定,确保只有一个线程可以初始化defaultExecutor
                    if (this.defaultExecutor == null) {
                        this.defaultExecutor = getDefaultExecutor(this.beanFactory);
                    }
                    targetExecutor = this.defaultExecutor;
                }
            }
        }
        
        // 如果没有合适的目标执行器,返回null
        if (targetExecutor == null) {
            return null;
        }
        
        // 如果目标执行器是AsyncListenableTaskExecutor则直接使用,否则使用适配器封装以确保它实现了AsyncListenableTaskExecutor
        //所以SpringBoot2.1之前最终会找到org.springframework.core.task.SimpleAsyncTaskExecutor
        executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
                (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
        
        // 将确定的执行器放入缓存,以便下次直接使用
        this.executors.put(method, executor);
    }
    // 返回确定的AsyncTaskExecutor
    return executor;
}


这个方法首先查找类型为 TaskExecutor 的Bean,如果有多个此类型的Bean,然后尝试查找名称为 “taskExecutor” 的Bean。如果这两种方式都没找到,那么将返回 null,表示没有找到适当的Bean来执行异步任务。这种情况下,AsyncExecutionInterceptor 将不会有执行器来处理异步方法。

// 会去 Spring 的容器中找有没有 TaskExecutor 或名称为 'taskExecutor' 为 Executor 的 
    @Nullable
    protected 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 {

	@Async
	public void testAsync1() {
		System.out.println(Thread.currentThread().getName());
		ThreadUtil.sleep(10, TimeUnit.DAYS);
	}

	@Async
	public void testAsync2() {
		System.out.println(Thread.currentThread().getName());
		ThreadUtil.sleep(10, TimeUnit.DAYS);
	}
}

注意需要在启动类上需要添加@EnableAsync注解,否则不会生效。

@RestController
public class TestController {

	@Autowired
	SyncService 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 {

	@Resource
	ThreadPoolTaskExecutor taskExecutor; //SpringBoot2.1之前直接注入会报错

	@SneakyThrows
	@RequestMapping("/testSync")
	public void testTomcatThreadPool() {
		ThreadPoolExecutor threadPoolExecutor = taskExecutor.getThreadPoolExecutor();
	}
}

通过debug可以看到默认的线程池参数如下,就是我们上面介绍的

image-20240521200925922

2.1版本以前

方式一:通过@Async注解–采用SimpleAsyncTaskExecutor

最主要的就是此时我们没有TaskExecutionAutoConfiguration自动配置类,也就不会去配置默认的线程池,根据上面分析的determineAsyncExecutor方法,此时@Async会去采用默认的SimpleAsyncTaskExecutor

@Service
public class SyncService {

	@Async
	public void testAsync1() {
		System.out.println(Thread.currentThread().getName());
		ThreadUtil.sleep(10, TimeUnit.DAYS);
	}

	@Async
	public void testAsync2() {
		System.out.println(Thread.currentThread().getName());
		ThreadUtil.sleep(10, TimeUnit.DAYS);
	}
}

注意需要再启动类上需要添加@EnableAsync注解,否则不会生效。

@RestController
public class TestController {

	@Autowired
	SyncService syncService;

	@SneakyThrows
	@RequestMapping("/testSync")
	public void testTomcatThreadPool() {
		syncService.testAsync1();
		syncService.testAsync2();
	}
}

验证输出结果,确实是采用了SimpleAsyncTaskExecutor线程池:

SimpleAsyncTaskExecutor-1
SimpleAsyncTaskExecutor-2

方式二:直接注入 ThreadPoolTaskExecutor

最主要的就是此时我们没有TaskExecutionAutoConfiguration自动配置类,也就不会去配置默认的线程池,此时直接注入就会报错,所以需要自己定义!

直接注入报错
@RestController
public class TestController {

	@Resource
	ThreadPoolTaskExecutor taskExecutor; //SpringBoot2.1之前直接注入会报错

	@SneakyThrows
	@RequestMapping("/testSync")
	public void testTomcatThreadPool() {
		ThreadPoolExecutor threadPoolExecutor = taskExecutor.getThreadPoolExecutor();
	}
}

此时发现在工程启动的时候就报错!

image-20240519225424332

解决:自定义一个线程池
@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

image-20240519225909829

默认的线程池好用吗?

TaskExecutionAutoConfiguration?

默认参数如下面所示:

  • 核心线程数 (corePoolSize): 8

  • 最大线程数 (maxPoolSize): Integer.MAX_VALUE (无限制)

  • 队列容量 (queueCapacity): Integer.MAX_VALUE (无限制)

  • 空闲线程保留时间 (keepAliveSeconds): 60秒

  • 线程池拒绝策略 (RejectedExecutionHandler): AbortPolicy(默认策略,超出线程池容量和队列容量时抛出RejectedExecutionException异常)

那么可能会有什么问题?

  1. 资源耗尽风险:由于最大线程数设置为Integer.MAX_VALUE,在高并发场景下,如果任务持续涌入,理论上可以创建无数线程,这将迅速消耗系统资源(如内存、CPU),可能导致服务器崩溃或极端性能下降。实际上,即使操作系统允许创建如此多的线程,这种设置也是极度不可取的。
  2. 响应性降低:虽然核心线程数设置为8是比较合理的起始点,但无限制的最大线程数意味着在高负载下,线程创建不会受限,这可能会影响到系统的响应时间和整体稳定性。过多的线程上下文切换会消耗大量CPU资源,降低处理效率。
  3. 队列无限增长:队列容量也设置为Integer.MAX_VALUE,这意味着当线程池中的线程都在忙碌且有新任务到来时,这些任务将不断堆积在队列中。如果生产速率持续高于消费速率,队列将无限增长,最终可能导致OutOfMemoryError。
  4. 异常处理策略过于简单粗暴:采用AbortPolicy作为拒绝策略,在线程池和队列都满载的情况下,新的任务提交将直接抛出RejectedExecutionException异常,而没有进行任何备份处理或降级策略,这可能会导致部分业务操作失败,且未被捕获处理的异常可能会影响到整个应用的稳定性。
  5. 缺乏灵活性和控制:对于不同的业务需求,线程池参数应当根据具体情况进行调整。例如,对响应时间敏感的服务可能需要更小的队列和更快的拒绝策略以避免任务长时间等待;而对于可延迟处理的任务,则可能需要更大的队列或不同的拒绝策略。

SimpleAsyncTaskExecutor

SimpleAsyncTaskExecutor 是Spring框架提供的一个简单的异步任务执行器,它并不是一个真正的线程池实现。它的主要特点是每次调用 #execute(Runnable) 方法时都会创建一个新的线程来执行任务。这意味着它不会重用线程,而是一次性的。

使用 SimpleAsyncTaskExecutor 可能会导致以下问题:

  1. 线程资源浪费: 每次执行任务时都创建新线程,这会导致线程的生命周期短暂,频繁地创建和销毁线程会消耗系统资源,包括CPU时间和内存。
  2. 系统资源耗尽: 在高并发场景下,如果大量任务同时提交,SimpleAsyncTaskExecutor 可能会创建大量线程,这可能会迅速耗尽系统资源,如线程数限制和内存资源。
  3. 缺乏线程管理: 真正的线程池通常包含线程管理机制,如线程重用、线程池大小控制、任务队列管理等。SimpleAsyncTaskExecutor 缺乏这些特性,因此在面对大量并发任务时可能无法有效地管理资源。
  4. 没有任务队列: 由于 SimpleAsyncTaskExecutor 不使用任务队列,它无法有效地缓冲任务。当任务提交速度超过执行速度时,新提交的任务可能会因为没有可用的线程而立即失败。
  5. 不适合长期运行的服务: 对于需要长期运行的服务,使用 SimpleAsyncTaskExecutor 可能会导致系统不稳定,因为它没有为长期运行的任务提供稳定的线程资源。

总结: SimpleAsyncTaskExecutor可以简单地用于异步任务执行,但由于它的设计限制,一般不推荐在生产环境中使用,特别是在要求高并发或资源使用高效的场景下。在这些情况下,更推荐使用可配置、可复用线程的线程池,如ThreadPoolTaskExecutor,因为它提供了更多的灵活性、效率和控制能力。

线程池核心线程数和最大线程数设置指南

线程池参数介绍
  • 核心线程数:线程池中始终活跃的线程数量。
  • 最大线程数:线程池能够容纳同时执行的最大线程数量。
线程数设置考虑因素
  1. CPU密集型任务:依赖CPU计算的任务,如循环计算等。
  2. IO密集型任务:任务执行过程中涉及等待外部操作,如数据库读写、网络请求、磁盘读写等。
CPU密集型任务的线程数设置
  • 推荐设置:核心数 + 1。
    • 原因:避免线程切换开销,同时允许一定程度的线程中断恢复。
IO密集型任务的线程数设置
  • 推荐设置:2 * CPU核心数。
    • 原因:IO操作期间,CPU可执行其他线程任务,提高资源利用率。
实际应用中的线程数计算
  • 使用工具(如Java的Visual VM)来监测线程的等待时间和运行时间。
  • 计算公式:(线程等待时间 / 线程总运行时间)+ 1 * CPU核心数。
生产环境中的线程数设置
  • 理论值与实际值可能存在差异,需要通过压力测试来确定最优线程数。
  • 压力测试:调整线程数,观察系统性能,找到最优解。
线程池参数设置建议
  • 核心业务应用:核心线程数设置为压力测试后的数值,最大线程数可以适当增加。
  • 非核心业务应用:核心线程数设置较低,最大线程数设置为压力测试结果
注意事项
  • 线程数设置需根据实际业务需求和系统环境进行调整。
  • 持续监控和优化是保证系统性能的关键。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1697519.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jenkins 构建 Maven 项目:构建服务器和部署服务器分离的情况

bash内容 #!/bin/bash#删除历史数据 rm -rf ruoyi-admin.jar# appname$1 appnamevideo.xxxxx.com #获取传入的参数 echo "arg:$appname"#获取正在运行的jar包pid # pidps -ef | grep $1 | grep java -jar | awk {printf $2} pidps -ef | grep $appname | grep java …

C# 深拷贝和浅拷贝

文章目录 1.深拷贝2.浅拷贝3.拷贝类4.浅拷贝的实现5.深拷贝实现5.1 浅拷贝对象&#xff0c;对引用类型重新一个个赋值5.2 反射实现5.3 利用XML序列化和反序列化实现 1.深拷贝 拷贝一个对象时&#xff0c;不仅仅把对象的引用进行复制&#xff0c;还把该对象引用的值也一起拷贝。…

聊聊ChatGPT的本质

这是鼎叔的第九十八篇原创文章。行业大牛和刚毕业的小白&#xff0c;都可以进来聊聊。 阶段性总结下我对ChatGPT的基础理解&#xff0c;算是一篇学习思考笔记吧。其中难免有很多不准确的&#xff0c;或过于简略的地方&#xff0c;将来再迭代学习。 OpenAI做ChatGPT的底层逻辑…

mfc140.dll丢失原因和mfc140.dll丢失修复办法分享

mfc140.dll是与微软基础类库&#xff08;Microsoft Foundation Classes, MFC&#xff09;紧密相关的动态链接库&#xff08;DLL&#xff09;文件。MFC是微软为C开发者设计的一个应用程序框架&#xff0c;用于简化Windows应用程序的开发工作。以下是mfc140.dll文件的一些关键属性…

spring boot 整合j2cache 项目启动警告 Redis mode [null] not defined. Using ‘single‘

好 之前的文章 spring boot 整合j2cache 基础操作 在spring boot环境中整合了 j2cache 我们 项目启动时 日志会有一个关键信息 Redis的模式 没有定义 默认使用 single Redis 的这个模式有四种 大家可以自己去网上找一下 做个了解 不用很纠结 我们直接在 j2cache.properties …

医院门诊互联电子病历|基于SSM+vue的医院门诊互联电子病历管理信息系统的设计与实现(源码+数据库+文档)

医院门诊互联电子病历管理信息系统 目录 基于SSM&#xff0b;vue的医院门诊互联电子病历管理信息系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2后台登录模块 5.2.1管理员功能 5.2.2用户功能 5.2.3医生功能 四、数据库设计 五、核心代码…

requests_html使用介绍

文章目录 一、requests_html 基本介绍二、requests_html 基本使用三、发送带有参数的请求四、图片抓取实战案例 一、requests_html 基本介绍 A、装库: pip install requests_html B、介绍: requests 和 requests_html 是同一个作者 二、requests_html 基本使用 A、导包: fro…

辐射度技术在AI去衣中的魅力与科学

引言&#xff1a; 在当今的数字化时代&#xff0c;人工智能正逐渐渗透到我们生活的方方面面。其中&#xff0c;AI去衣技术作为一项颇具争议但又不失其科技创新的应用&#xff0c;正引起越来越多的关注和讨论。而在实现高质量图像渲染的过程中&#xff0c;辐射度技术凭借其卓越的…

Linux指令初识

ls:显示当前目录底下的指定文件或目录 ls -l更详细的信息 ls -a显示当前目录下的所有文件 命令中的选项可以一次传递多个 ,例如&#xff1a;ls -al 命令和选项有必须一个或多个空格 以.开头的文件&#xff0c;为隐藏文件ls -a可以看到,ls -l看不见 支持命令拼在一起&#…

Rust开源Web框架Salvo源码编译

1.克隆源码: https://github.com/salvo-rs/salvo.git 2.进入salve目录并运行cargo build编译 编译成功 3.编译生成的库 4.安装salve-cli git clone --recursive https://github.com/salvo-rs/salvo-cli.git 编译salve-cli

stm32-PWM输出比较配置

配置流程 1.RCC开启时钟 2.时钟源选择和配置时基单元 这一部分上一篇有写&#xff0c;可以参考一下上一篇的内容&#xff0c;此处不多赘述了。 原文链接&#xff1a;https://blog.csdn.net/m0_74246768/article/details/139048136 3.配置输出比较单…

链表类型的无界阻塞线程安全队列-ConcurrentLinkedQueue(FIFO)

ConcurrentLinkedQueue是非阻塞线程安全(volatile不能完全保证线程安全)的队列,适用于“高并发”的场景。是一个基于链表节点的无界线程安全队列,按照 FIFO(先进先出,尾先进头先出)原则对元素进行排序。队列元素中不可以放置null元素(内部实现的特殊节点除外)。 volati…

【WEEK13】 【DAY5】Shiro第五部分【中文版】

2024.5.24 Friday 接上文【WEEK13】 【DAY4】Shiro第四部分【中文版】 目录 15.7.Shiro请求授权的实现15.7.1.修改ShiroConfig.java15.7.1.1.添加一行验证授权的代码15.7.1.2.重启 15.7.2.修改MyController.java15.7.3.修改ShiroConfig.java15.7.4.重启15.7.5.修改UserRealm.ja…

风电机组的振动控制

文章目录 0. 背景1. 原文记录 0. 背景 混塔机组的频率大概是目前业内遇见的比较普遍的通病。最近在了解风电机组振动控制的知识&#xff0c;看到一篇科普性质的文章&#xff0c;感觉不错&#xff0c;所以记录下来。想要看原文的点击这里。感谢原作者。 1. 原文记录

在IDEA中配置servlet(maven配置完成的基础下)

在IDEA中配置servlet&#xff08;maven配置完成的基础下&#xff09; 1.先新建一个项目 2.选择尾巴是webapp的&#xff0c;名称自定义 3.点击高级设置&#xff0c;修改组id 点击创建&#xff0c;等待jar包下载完成。在pom.xml中配置以下 <dependency><groupId>ja…

【C++算法】BFS解决拓扑排序问题相关经典算法题

1.铺垫概念 ⭐有向无环图&#xff08;DAG图&#xff09; 有向无环图是一种特殊的图数据结构。在这样的图中&#xff0c;节点之间通过有向边连接&#xff0c;表示从一个节点到另一个节点的单向关系&#xff0c;并且不存在任何形式的环路&#xff0c;即没有路径可以让你从一个节…

CLIP 论文的关键内容

CLIP 论文整体架构 该论文总共有 48 页&#xff0c;除去最后的补充材料十页去掉&#xff0c;正文也还有三十多页&#xff0c;其中大部分篇幅都留给了实验和响应的一些分析。 从头开始的话&#xff0c;第一页就是摘要&#xff0c;接下来一页多是引言&#xff0c;接下来的两页就…

PMBOK® 第六版 项目经理的角色

项目经理普遍是一个责任大但权力有限的角色&#xff0c;是一个综合的中层领导者&#xff0c;负责项目从启动到收尾的全过程。他需要整合项目管理的各个方面&#xff0c;以确保项目目标的实现&#xff0c;并满足相关方的期望和需求。在工作中&#xff0c;项目经理大部分时间都用…

【全开源】海报在线制作系统源码(ThinkPHP+FastAdmin+UniApp)

打造个性化创意海报的利器 引言 在数字化时代&#xff0c;海报作为一种重要的宣传媒介&#xff0c;其设计质量和效率直接影响着宣传效果。为了满足广大用户对于个性化、高效制作海报的需求&#xff0c;海报在线制作系统源码应运而生。本文将详细介绍海报在线制作系统源码的特…

MVCC 原理分析、MySQL是如何解决幻读的

文章目录 一、前言回顾1.1 事务四大特性ACID1.2 并发事务问题1.3 事务隔离级别 二、MVCC2.1 为什么使用MVCC2.2 基本概念——当前读、快照读、MVCC2.2.1 当前读2.2.2 快照读2.2.3 MVCC 2.3 隐藏字段—— TRX_ID、ROLL_PTR2.4 undo log2.4.1 介绍2.4.2 版本链 2.5 Read View读视…