Spring Boot @Scheduled使用NANOSECONDS抛出IllegalStateException异常
问题描述
需要实现纳秒或微秒级精度的定时任务调度,在仅引入最小依赖的Spring Boot项目中,使用@Scheduled(fixedRateString ="600000", timeUnit =TimeUnit.NANOSECONDS)配置定时方法时,程序启动抛出如下异常:
Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'test': Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required
项目核心复现代码如下:
@SpringBootApplication @EnableScheduling public class SchedulingDemoApplication { public static void main(String[] args) { SpringApplication.run(SchedulingDemoApplication.class, args); } @Component public class Task { @Scheduled(fixedRateString ="600000", timeUnit =TimeUnit.NANOSECONDS) public void test() { LocalDateTime time = LocalDateTime.now(); System.out.println("running in "+time); } } }
实际测试发现,将注解中timeUnit配置修改为TimeUnit.SECONDS或TimeUnit.MILLISECONDS时,项目启动无任何报错。
问题产生原因
- 启动报错的直接原因是Spring Framework低版本的已知bug:
@Scheduled的timeUnit属性在Spring 5.3版本才正式加入,5.3.17之前的版本存在校验逻辑漏洞——当timeUnit设置为微秒、纳秒这类精度高于毫秒的单位时,负责解析调度注解的ScheduledAnnotationBeanPostProcessor会误判没有配置任何有效的调度参数,直接抛出上述异常。当timeUnit设为秒、毫秒时不会触发这个有问题的校验分支,所以启动正常。 - 就算升级版本修复了启动报错,Spring自带的
@Scheduled也做不到纳秒、微秒级的调度精度。它默认用的调度器是基于JDK的ScheduledThreadPoolExecutor实现的,调度触发完全依赖操作系统的线程调度能力,普通非实时内核的Windows、Linux系统本身的线程调度延迟就有1~10毫秒,再加上GC停顿、系统负载波动影响,实际调度误差基本都在毫秒级,根本不可能稳定达到微秒、纳秒的精度。配置的600000纳秒换算后只有0.6毫秒,这个间隔比默认调度器的最小可靠调度间隔还小,实际运行会出现严重的任务堆积、触发时间漂移,完全达不到预期效果。
解决方案
- 仅修复启动报错:直接把Spring Framework升级到5.3.17及以上版本,对应Spring Boot升级到2.6.5及以上版本即可,该版本已经修复了
timeUnit的校验bug。 - 要实现真正的微秒/纳秒级高精度调度,不要使用Spring自带的
@Scheduled组件,根据实际场景选择方案:- 如果程序跑在普通服务器/PC的非实时系统上,根本没法稳定实现微秒级以上的定时精度,建议重新评估业务的精度要求,绝大多数业务场景毫秒级精度完全够用。
- 要是必须在通用系统上做亚毫秒级调度,可以用高分辨率时间戳配合忙等待(Busy Spin)实现,但要注意这种方式会独占一个CPU核心,只适合CPU资源充足、定时任务逻辑极轻量的场景。
- 要是需要稳定的微秒、纳秒级定时能力,必须搭配实时操作系统、专用硬件定时卡才能实现,纯上层应用代码做不到这个精度。
内容的提问来源于stack exchange,提问作者hamidreza75
相关产品推荐
相关产品推荐

