Framework: 6.1.2 Boot: 3.2.1
With spring.threads.virtual.enabled=true
and otherwise default config, all methods annotated with @Scheduled(fixedDelay = ...)
are executed on the same virtual thread, preventing multiple scheduled methods from running at the same time. cron
and fixedRate
tasks are not limited like this and run on separate threads (but of course allow executions of the same task to overlap).
The documentation states:
There is also a new option called
SimpleAsyncTaskScheduler
which is aligned with JDK 21’s Virtual Threads, using a single scheduler thread but firing up a new thread for every scheduled task execution.
This behaviour is most likely due to #31334, but shouldn't separate fixed-delay tasks still be allowed to run in parallel? The following demonstrates this behaviour:
@Slf4j
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Scheduled(fixedDelay = 1000)
public void sched1() throws InterruptedException {
log.info("Starting sched1, sleeping for five seconds. Thread: {}", Thread.currentThread());
TimeUnit.SECONDS.sleep(5L);
log.info("Done with sched1.");
}
@Scheduled(fixedDelay = 1000)
public void sched2() throws InterruptedException {
log.info("Starting sched2, sleeping for five seconds. Thread: {}", Thread.currentThread());
TimeUnit.SECONDS.sleep(5L);
log.info("Done with sched2.");
}
}
This produces output like this, showing that even while one task is blocked, the other one has to wait:
2023-12-23T22:34:08.092+01:00 INFO 23560 --- [ scheduling-1] com.example.demo.DemoApplication : Starting sched1, sleeping for five seconds. Thread: VirtualThread[#50,scheduling-1]/runnable@ForkJoinPool-1-worker-4
2023-12-23T22:34:13.093+01:00 INFO 23560 --- [ scheduling-1] com.example.demo.DemoApplication : Done with sched1.
2023-12-23T22:34:13.093+01:00 INFO 23560 --- [ scheduling-1] com.example.demo.DemoApplication : Starting sched2, sleeping for five seconds. Thread: VirtualThread[#50,scheduling-1]/runnable@ForkJoinPool-1-worker-4
2023-12-23T22:34:18.094+01:00 INFO 23560 --- [ scheduling-1] com.example.demo.DemoApplication : Done with sched2.
Is this behaviour intentional? If so, I would have expected a note about this in the docs...
Comment From: jhoeller
This is intentional since the effort of managing multiple scheduler threads just for the potential concurrent execution of multiple fixed-delay threads seems not worth the overhead.
Effectively, fixed-delay semantics are discouraged with virtual threads since you do not get a thread-per-task handoff there in any case. This is currently just being mentioned in the SimpleAsyncTaskScheduler
javadoc:
"NOTE: Scheduling with a fixed delay enforces execution on the single scheduler thread, in order to provide traditional fixed-delay semantics! Prefer the use of fixed rates or cron triggers instead which are a better fit with this thread-per-task scheduler variant."
I understand that this is not very obvious. Where would you expect it to be mentioned so that it is easier to find?
Comment From: kzander91
I see, thank you.
I would have expected this in the Javadoc of the @Scheduled
annotation, and somewhere in the task scheduling section of the reference docs I linked above.
This is probably also an issue with Boot's documentation, because the task scheduling properties also don't mention this limitation. spring.task.scheduling.simple.concurrency-limit
is -1
by default, with its description stating "-1 indicates no concurrency limit at all.", but actually, fixed-delay tasks are subject to a concurrency limit of 1 when virtual threads are enabled.