i am trying to execute a task at a fixed rate using the @Scheduled annotation in java spring. however, it seems that by default spring will not execute a fixedRate task at a fixed rate if the task is slower than the rate. is there some setting i can add to my spring configuration to change this behavior?
example:
@Service
public class MyTask{
@Scheduled(fixedRate = 1000)
public void doIt(){
// this sometimes takes >1000ms, in which case the next execution is late
...
}
}
i have a work-around, but it seems less than ideal. basically, i just replace the default single-thread executor with a thread pool, then i have a scheduled method call an async method since the @Async annotation allows concurrent executions:
@Service
public class MyTask{
@Async
public void doIt(){
// this sometimes takes >1000ms, but the next execution is on time
...
}
}
@Service
public class MyTaskScheduler{
...
@Scheduled(fixedRate = 1000)
public void doIt(){
myTask.doIt();
}
}
@Configuration
@EnableScheduling
@EnableAsync
public class MySpringJavaConfig{
@Bean(destroyMethod = "shutdown")
public Executor taskScheduler() {
return Executors.newScheduledThreadPool(5);
}
}
boring details of my real-world scenario: in my production code i have a task that takes between 10ms and 10 minutes depending on the current workload. ideally, i would like to capture a new thread from the pool every 1000ms so that the number of concurrent threads increases with the workload. obviously i have an upper limit on threads in place (among other controls) to keep things from getting out of hand.
by default, spring uses a single-threaded Executor. so no two @Scheduled tasks will ever overlap. even two @Scheduled methods in completely unrelated classes will not overlap simply because there is only a single thread to execute all @Scheduled tasks.
We can easily schedule tasks in spring boot by using @Scheduled annotation. And then we need to enable scheduling by adding @EnableScheduling annotation to a spring configuration class. Spring uses ThreadPoolTaskScheduler for scheduled tasks, which internally delegates to a ScheduledExecutorService.
We can turn any method in a Spring bean for scheduling by adding the @Scheduled annotation to it. The @Scheduled is a method-level annotation applied at runtime to mark the method to be scheduled. It takes one attribute from cron , fixedDelay , or fixedRate for specifying the schedule of execution in different formats.
The TaskScheduler
API (which backs some of the Spring Scheduling behavior) seems to be defined to prevent the behavior you are requesting
Schedule the given
Runnable
, invoking it at the specified execution time and subsequently with the given period.Parameters
- period the interval between successive executions of the task (in milliseconds)
subsequently and successive seem to indicate that the next execution will only occur after the current execution is complete.
What's more, ScheduledExecutorService#scheduleAtFixedRate(..)
(which the built-in TaskScheduler
implementations use) also says
If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.
So there's another layer of the implementation that prevents the behavior you want.
A possible solution, and one I don't recommend since the API doesn't seem to be built around it, is to define and provide your own TaskScheduler
that does run the task concurrently. Look into @EnableScheduling
and SchedulingConfigurer
for how to register a TaskScheduler
.
the best solution i have found so far is to simply use a delegate to make the method calls async. this is only preferable because it allows me to declare the schedule in the same class as the method which does the work:
@Service
public class AsyncRunner {
@Async
public void run(Runnable runnable) {
runnable.run();
}
}
@Service
public class MyTask{
...
@Scheduled(fixedRate = 1000)
public void scheduleIt(){
asyncRunner.run(this::doIt);
}
public void doIt(){
// this sometimes takes >1000ms, but the next execution is on time
...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With