Java 8

Spring Boot v2.7.3

Windows 11

--

 

前文初步介绍了 Spring Boot 中的线程,以及使用 @Async 时使用自定义的线程池。

本文主要介绍Spring Boot项目中的线程池的配置以及默认线程池的创建等。

 

建立Web项目:webdemo

依赖:spring-boot-starter-web、lombok

端口:10000

使用默认的 tomcat 服务器


直接启动,检查线程:http请求处理

存在10个 http-nio-10000-exec- 开头的线程。

java springboot 创建线程 springboot线程数_线程

注意,截图来自 jvisualvm.exe 工具。

 

为何是10个?在Spring Boot文档中,介绍了 下面的参数: 发布于博客园

server.tomcat.threads.min-spare 默认值10

server.tomcat.threads.max 默认值200

java springboot 创建线程 springboot线程数_线程池_02

将 server.tomcat.threads.min-spare 修改为 5,此时,线程数量变为 5个:

java springboot 创建线程 springboot线程数_tomcat_03

注意,如果使用其它的嵌入式Web服务器,会有其它的配置。

 

配置异步线程池:AsyncConfigurer接口

结合 @EnableAsync、@Async 一起使用。

@Configuration
@Slf4j
public class AppAsyncConfig implements AsyncConfigurer {

	@Override
	public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		
		executor.initialize();
		log.info("getAsyncExecutor 1 executor={}", executor);
		
		executor.setCorePoolSize(10);
		executor.setQueueCapacity(100);
		executor.setMaxPoolSize(20);
		
		executor.setKeepAliveSeconds(30);
		
		executor.setThreadNamePrefix("async-");
		
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
		
		log.info("getAsyncExecutor 2 executor={}", executor);
		return executor;
	}
	
}

此时,异步线程的名称前缀为 “async-”开头,如下:

java springboot 创建线程 springboot线程数_线程池_04

建立了一个线程池,此线程池是在 服务启动后,第一次调用接口时创建的。

注,前文中介绍了 直接使用 @Bean注解 和  ThreadPoolTaskExecutor 生成异步线程池的功能。 发布于博客园

另外,AsyncConfigurer接口 还有一个方法 getAsyncUncaughtExceptionHandler 可以用来做异常处理。

 

调度程序线程及设置

使用 @EnableScheduling、@Scheduled 可以启动调度程序。

入口类使用 @EnableScheduling,建立一个调度类:AppSchecule

@Component
@Slf4j
public class AppSchecule {
	
	public static AtomicInteger cnt = new AtomicInteger();
	
	@Scheduled(initialDelay = 5, fixedDelay = 30, timeUnit = TimeUnit.SECONDS)
	public void outputTime1() {
		log.info("outputTime1 cnt={}", cnt.incrementAndGet());
	}

}

日志输出效果:启动后等5秒才执行,之后每隔30秒执行一次

INFO 38004 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=1
INFO 38004 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=2
INFO 38004 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=3
INFO 38004 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=4
INFO 38004 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=5

从下图可以看到,有一个 scheduling- 开头的线程: 发布于博客园

java springboot 创建线程 springboot线程数_线程池_05

疑问:这个线程是来自哪里呢?怎么配置的呢? 发布于博客园

 

在一个 调度Component中使用多个 @Scheduled 注解

@Component
@Slf4j
public class AppSchecule {
	
	public static AtomicInteger cnt = new AtomicInteger();
	
	public static AtomicInteger cnt2 = new AtomicInteger();
	
	@Scheduled(initialDelay = 5, fixedDelay = 30, timeUnit = TimeUnit.SECONDS)
	public void outputTime1() {
		log.info("outputTime1 cnt={}", cnt.incrementAndGet());
	}
	
	@Scheduled(initialDelay = 5, fixedDelay = 12, timeUnit = TimeUnit.SECONDS)
	public void outputTime2() {
		log.info("outputTime2 cnt2={}", cnt2.incrementAndGet());
	}

}

日志输出效果:两者在 同一个调度线程中。 发布于博客园

INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=1
INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=1
INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=2
INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=3
INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=2
INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=4
INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=5
INFO 40808 --- [   scheduling-1] com.lib.webdemo.AppSchecule              : outputTime1 cnt=3

调度线程池 可以使用 “spring.task.scheduling” 开头的属性进行配置。

java springboot 创建线程 springboot线程数_筑基_06

原来,默认的调度线程只有一个。

将 spring.task.scheduling.pool.size 改为 5,再进行测试:两个@Scheduld 注解的任务在不同线程进行处理了。 发布于博客园

spring:
  profiles:
    active:
    - dev
  task:
    scheduling:
      # 调度线程数量由 1 改为 5
      pool:
        size: 5
      # 线程名前缀
      thread-name-prefix: schd-

日志截图:

java springboot 创建线程 springboot线程数_筑基_07

java springboot 创建线程 springboot线程数_线程_08

疑问:

调度的最佳实践是什么呢?怎么做好配置呢? 发布于博客园

之前听说@Scheduld 调度的任务不生效(不执行),又是怎么回事?

 

程序中配置调度线程池

上面介绍了 配置中配置调度线程池,本节展示 程序中配置。

程序中配置的 优先级高于 配置文件中配置的。 发布于博客园

@Bean
	public TaskScheduler taskScheduler() {
		TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
		
		return builder
				.poolSize(20)
				.threadNamePrefix("sch2-")
				.build();
	}

启动后的日志:线程前缀为 “sch2-” 了,而不是配置文件中的 “schd-”。

[        sch2-10] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=6
[         sch2-5] com.lib.webdemo.AppSchecule2             : outputTime3 cnt=3
[        sch2-11] com.lib.webdemo.AppSchecule2             : outputTime4 cnt2=5
[         sch2-2] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=7
[         sch2-2] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=8
[        sch2-12] com.lib.webdemo.AppSchecule2             : outputTime4 cnt2=6
[        sch2-13] com.lib.webdemo.AppSchecule              : outputTime1 cnt=4
[         sch2-6] com.lib.webdemo.AppSchecule2             : outputTime3 cnt=4
[        sch2-14] com.lib.webdemo.AppSchecule              : outputTime2 cnt2=9

 发布于博客园

参考资料

1、SpringBoot关于@Async线程池配置


Quote:“关于修改 @Async默认的线程池 ,我们仅仅需要实现一个 AsyncConfigurer 类”

2、7.22. Task Execution and Scheduling

官方文档

3、spring boot @Scheduled未生效原因以及相关坑、及相对其他定时任务架构的优势


4、谨慎使用SpringBoot中的@Scheduled注解

https://cloud.tencent.com/developer/article/1835370