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- 开头的线程。
注意,截图来自 jvisualvm.exe 工具。
为何是10个?在Spring Boot文档中,介绍了 下面的参数: 发布于博客园
server.tomcat.threads.min-spare 默认值10
server.tomcat.threads.max 默认值200
将 server.tomcat.threads.min-spare 修改为 5,此时,线程数量变为 5个:
注意,如果使用其它的嵌入式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-”开头,如下:
建立了一个线程池,此线程池是在 服务启动后,第一次调用接口时创建的。
注,前文中介绍了 直接使用 @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- 开头的线程: 发布于博客园
疑问:这个线程是来自哪里呢?怎么配置的呢? 发布于博客园
在一个 调度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” 开头的属性进行配置。
原来,默认的调度线程只有一个。
将 spring.task.scheduling.pool.size 改为 5,再进行测试:两个@Scheduld 注解的任务在不同线程进行处理了。 发布于博客园
spring:
profiles:
active:
- dev
task:
scheduling:
# 调度线程数量由 1 改为 5
pool:
size: 5
# 线程名前缀
thread-name-prefix: schd-
日志截图:
疑问:
调度的最佳实践是什么呢?怎么做好配置呢? 发布于博客园
之前听说@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