Problems using spring @Scheduled
This happens because @Async task is submitted by default Scheduling executor and its size is 1 by default.
I have modified submit method of the AsyncTaskExecutor
executor:
@Bean
AsyncConfigurer asyncConfigurer() {
return new AsyncConfigurer() {
@Override
public AsyncTaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(){
@Override
public <T> Future<T> submit(Callable<T> task) {
System.out.println("async task was started by thread -- "+Thread.currentThread().getName());
return super.submit(task);
}
};
executor.setThreadNamePrefix("custom-async-exec");
executor.setCorePoolSize(2);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
};
}
And output.
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
async task was started by thread -- scheduling-1
So as there is 1 thread into default Shedulers pool scheduling-1
and whenever its busy can't start/submit new @Async
tasks. define @Bean
ThreadPoolTaskExecutor or add spring.task.scheduling.pool.size=x
.
EDIT
Here is simple test for visualization:
@Component
public static class Jobs{
@Scheduled(fixedDelay = 1500)
@Async
public void job1(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Scheduled(fixedDelay = 1500)
@Async
public void job2(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Scheduled(initialDelay = 10000, fixedDelay = 5000)
public void blocking(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
visualization from visualvm
Red 'arrows' shows the point blocking()
job kicks in. and while scheduling-1
Thread
blocked on this there is no way to submit job1()
and job2()
also
Scheduled tasks are processed by the ThreadPoolTaskScheduler
, which has a default pool size of 1. Only when they are annotated as @Async
, execution is passed into the AsyncTaskExecutor
, which for you configured a dedicated executor with a larger pool size.
To configure the ThreadPoolTaskScheduler
within a @Configuration
class:
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(50);
return scheduler ;
}
All @Scheduled
marked invocations will use the default single thread executor to schedule tasks (async or otherwise).
All @Async
tasks are handed off to different aysnc threadpool executor for executions with AOP interceptor.
I think your confusion comes from the fact async methods return immediately but when deleteOldData is run, it is running synchronously as there is only one thread, it blocks the execution of any other scheduled tasks.
Because you have default threadpool (single thread) for the scheduled tasks they are scheduled one after the another.
The other methods annotated with @Async they execute even if it finishes or not. In some cases, I have two methods of executing at the same time. But when deleteOldData is executing, the async methods stop to run until it finishes. This what I'm not understanding, sorry :/ –
This is different from scheduling - This is where your async executor comes into play and they are run concurrently.
You can fix this in one of two ways:
You can use spring.task.scheduling.pool.size=10
in application properties to set the pool size of task scheduler.
Alternatively, use different tasks schedulers. Keep using default scheduler for @Scheduled
task and configure something like below for async tasks ( remove scheduled annotation )
There is an enhancement requested to pass task scheduler to the @Scheduled
annotation until then you have to schedule tasks manually.
Register a new task scheduler for async invocations and schedule the methods in the post construct stage. Something like
Configuration
@Bean("asyncTaskScheduler")
public TaskScheduler asyncTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
Services
@Autowired
private TaskScheduler asyncTaskScheduler;
@PostConstruct
void schedule() {
asyncTaskScheduler.scheduleAtFixedRate(this::checkAvailableTasks, 20000L);
asyncTaskScheduler.scheduleAtFixedDelay(this::checkBrokenEngines, 20000L);
}
@Async
public void checkBrokenEngines() {...}
@Async
public void checkAvailableTasks() throws Exception {...}