bigywater.tech
Spring · 25

@Scheduled:默认单线程调度器的串行与阻塞

@Scheduled 的任务到底跑在哪个线程?一个慢任务会不会挡住别的任务?fixedRate 和 fixedDelay 在任务变慢时有什么区别?任务抛异常会怎样?加上 @Async 呢?这篇在 Spring Framework 7.0.9 上用带时间戳的日志逐项验证。

1 个线程
纯 Spring 与 Boot 默认的调度器线程数(慢任务期间其它定时任务全被挡住)
5 次
一个 1000 ms 的慢任务结束后,被挡住的 fixedRate = 200 ms 任务在 30 ms 内补跑的次数
506 对 806 ms
任务耗时 500 ms、间隔 300 ms 时,fixedRate 与 fixedDelay 的实际周期

默认单线程

实测 · 本机执行输出
  --- c1
    104 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
    297 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
    499 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
    506 ms  slow      [pool-1-thread-1] 开始,耗时 1000 ms
   1511 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   1517 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   1528 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   1534 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   1541 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   1699 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   1898 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   2095 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   2299 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms
   2495 ms  fast      [pool-1-thread-1] 开始,耗时 5 ms

慢任务挡住所有任务,结束后一口气补跑错过的次数。

加大线程池

实测 · 本机执行输出
  --- c1p
    108 ms  fast      [sched-1] 开始,耗时 5 ms
    304 ms  fast      [sched-2] 开始,耗时 5 ms
    500 ms  fast      [sched-1] 开始,耗时 5 ms
    500 ms  slow      [sched-2] 开始,耗时 1000 ms
    703 ms  fast      [sched-1] 开始,耗时 5 ms
    904 ms  fast      [sched-1] 开始,耗时 5 ms
   1104 ms  fast      [sched-1] 开始,耗时 5 ms
   1304 ms  fast      [sched-1] 开始,耗时 5 ms
   1504 ms  fast      [sched-1] 开始,耗时 5 ms
   1704 ms  fast      [sched-1] 开始,耗时 5 ms
   1904 ms  fast      [sched-1] 开始,耗时 5 ms
   2104 ms  fast      [sched-1] 开始,耗时 5 ms
   2304 ms  fast      [sched-1] 开始,耗时 5 ms
   2504 ms  fast      [sched-1] 开始,耗时 5 ms

调度器线程池大小设成 3,快任务不再被挡。

Boot 的默认

实测 · 本机执行输出
  Boot 里的 TaskScheduler Bean:[taskScheduler]
  --- boot
    106 ms  fast      [scheduling-1] 开始,耗时 5 ms
    304 ms  fast      [scheduling-1] 开始,耗时 5 ms
    501 ms  fast      [scheduling-1] 开始,耗时 5 ms
    507 ms  slow      [scheduling-1] 开始,耗时 1000 ms
   1511 ms  fast      [scheduling-1] 开始,耗时 5 ms
   1518 ms  fast      [scheduling-1] 开始,耗时 5 ms
   1524 ms  fast      [scheduling-1] 开始,耗时 5 ms
   1530 ms  fast      [scheduling-1] 开始,耗时 5 ms
   1537 ms  fast      [scheduling-1] 开始,耗时 5 ms
   1703 ms  fast      [scheduling-1] 开始,耗时 5 ms
   1900 ms  fast      [scheduling-1] 开始,耗时 5 ms
   2098 ms  fast      [scheduling-1] 开始,耗时 5 ms
   2300 ms  fast      [scheduling-1] 开始,耗时 5 ms
   2501 ms  fast      [scheduling-1] 开始,耗时 5 ms

Boot 的 taskScheduler 池大小也是 1。

fixedRate 与 fixedDelay

实测 · 本机执行输出
  --- c2a
      2 ms  fixedRate [pool-1-thread-1] 开始,耗时 500 ms
    509 ms  fixedRate [pool-1-thread-1] 开始,耗时 500 ms
   1015 ms  fixedRate [pool-1-thread-1] 开始,耗时 500 ms
   1520 ms  fixedRate [pool-1-thread-1] 开始,耗时 500 ms
   2025 ms  fixedRate [pool-1-thread-1] 开始,耗时 500 ms
   2530 ms  fixedRate [pool-1-thread-1] 开始,耗时 500 ms
  --- c2b
      2 ms  fixedDelay [pool-1-thread-1] 开始,耗时 500 ms
    814 ms  fixedDelay [pool-1-thread-1] 开始,耗时 500 ms
   1620 ms  fixedDelay [pool-1-thread-1] 开始,耗时 500 ms
   2426 ms  fixedDelay [pool-1-thread-1] 开始,耗时 500 ms

任务比间隔慢时,fixedRate 背靠背执行,fixedDelay 在每次结束后再等。

异常

实测 · 本机执行输出
  LOG ERROR TaskUtils$LoggingErrorHandler: Unexpected error occurred in scheduled task
  --- c5
      3 ms  boom#1    [pool-1-thread-1] 开始,耗时 0 ms
    301 ms  boom#2    [pool-1-thread-1] 开始,耗时 0 ms
    601 ms  boom#3    [pool-1-thread-1] 开始,耗时 0 ms
    904 ms  boom#4    [pool-1-thread-1] 开始,耗时 0 ms
   1202 ms  boom#5    [pool-1-thread-1] 开始,耗时 0 ms
   1505 ms  boom#6    [pool-1-thread-1] 开始,耗时 0 ms
   1805 ms  boom#7    [pool-1-thread-1] 开始,耗时 0 ms

抛异常只记日志,后续调度继续。

@Async

实测 · 本机执行输出
  --- c6
    110 ms  asyncSlow [SimpleAsyncTaskExecutor-1] 开始,耗时 700 ms
    299 ms  asyncSlow [SimpleAsyncTaskExecutor-2] 开始,耗时 700 ms
    503 ms  asyncSlow [SimpleAsyncTaskExecutor-3] 开始,耗时 700 ms
    703 ms  asyncSlow [SimpleAsyncTaskExecutor-4] 开始,耗时 700 ms
    903 ms  asyncSlow [SimpleAsyncTaskExecutor-5] 开始,耗时 700 ms
   1102 ms  asyncSlow [SimpleAsyncTaskExecutor-6] 开始,耗时 700 ms
   1303 ms  asyncSlow [SimpleAsyncTaskExecutor-7] 开始,耗时 700 ms
   1498 ms  asyncSlow [SimpleAsyncTaskExecutor-8] 开始,耗时 700 ms

触发和执行分开,执行会重叠,线程可能无限增长。

分步演示

@Scheduled
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。第一、二、四、五、六节用不带 Boot 的 Spring Framework 7.0.9,第三节用 Spring Boot 4.0.8 的 @EnableAutoConfiguration;时间戳每次运行有几 ms 的抖动,补跑的具体次数取决于被挡住的时长(这次是 5 次)
    • 没有验证:cron 表达式的触发精度与时区;多实例部署下同一个任务的重复执行(见后面的 ShedLock 篇);spring.task.scheduling.* 配置项;ScheduledTaskRegistrar 的动态注册;应用关闭时正在执行的定时任务(见优雅停机篇);调度器线程池的 shutdown 等待时间
    京ICP备2026067502号-1京公网安备11010502063487号