@Schedule is a common timed task annotation. It is generally used in business logic that needs to be executed regularly. It is mostly used for single-machine tasks. If it is used in distributed tasks, it is necessary to ensure data consistency through distributed locks.
If there are multiple scheduled tasks defined by the @Schedule annotation, are they executed concurrently or serially?
The timed tasks defined by @Schedule should also have a thread pool, such as ScheduleThreadPool in the four common thread pools
Four common thread pools:
- SingleThreadPool: single thread thread pool
- FixedThreadPool: Thread pool for fixed threads
- ScheduleThreadPool: Thread pool for scheduled tasks
- CachedThreadPool: A thread pool with an infinite number of cached threads but an infinite number of threads
So is it to execute multiple timing tasks concurrently or only one thread executes timing tasks serially?
If it is executed serially, then there will be serious problems! Scheduled tasks cannot be executed on time and there is a risk of blocking
So how to make the timed tasks defined by @Schedule execute concurrently with multiple threads?
test
In the same class, define multiple timing tasks through @Schedule to check whether multiple timing tasks are executed using the same thread and whether they are executed in parallel
- sample code
@Component public class ScheduleTest { @Scheduled(cron = "0/30 * * * * ?") public void task1() { System.out.println("task1 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } @Scheduled(cron = "0/30 * * * * ?") public void task2() { System.out.println("task2 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } @Scheduled(cron = "0/50 * * * * ?") public void task3() { System.out.println("task3 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } }
- output
task2 start 40 scheduling-1 task1 start 40 scheduling-1 task3 start 40 scheduling-1
As you can see, the three tasks use the same thread
- test again
By writing an infinite loop in a certain task, let this task continue to execute, and then see if other scheduled tasks will still execute
@Scheduled(cron = "0/30 * * * * ?") public void task1() { System.out.println("task1 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } @Scheduled(cron = "0/30 * * * * ?") public void task2() { while (true) { System.out.println("task2 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } }
It can be seen from the output that the task of task1 has not been executed, and the task of task2 has been executed.
Source code analysis
The processing class of the @Schedule annotation is in the class org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor
- In this method, it indicates the business logic to be executed after the bean is initialized
-
In the this.registrar.afterPropertiesSet(); method at the bottom of the finishRegistration method, it means to start calling the post-processor.
-
The post-processor starts to execute all the scheduled tasks scanned above, org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks
-
As you can see from the scheduleTasks method, during execution, it will check whether there is a custom thread pool, if not, then a SingleThreadSchedulePool will be created as a thread pool for execution
[External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-vLHH8D3n-1651588669675)(https://cdn.jsdelivr.net/gh/chenliang15405/picture/hub/cs /image-20220503221258957.png)]
Seeing this, it is already obvious that when using, there is no custom thread pool, so when executing tasks, Spring will automatically create a thread pool to execute, but the default thread pool is a core thread of 1 single-threaded thread pool
solution
-
The first: global configuration
This method is equivalent to the aspect method. For the unified timing task pair processing, there is no need to pay attention to the thread pool configuration of each timing task.
@Configuration @EnableScheduling public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors())); } }
-
The second: custom thread pool
In this way, different thread pools can be configured according to the business logic and the importance of timed tasks, and different tasks can be isolated without affecting each other. The timed tasks configured in this way are more flexible
-
configuration class
@EnableAsync @Configuration public class AsyncScheduleConfig { @Bean("scheduleExecutor") public Executor myAsync() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //maximum number of threads executor.setMaxPoolSize(100); //number of core threads executor.setCorePoolSize(10); //The size of the task queue executor.setQueueCapacity(10); //thread prefix executor.setThreadNamePrefix("async-schedule-"); //thread survival time executor.setKeepAliveSeconds(60); /** * Deny Handling Policy * CallerRunsPolicy(): Let the caller thread run, such as the main thread. * AbortPolicy(): Throws an exception directly. * DiscardPolicy(): Discard directly. * DiscardOldestPolicy(): Discard the oldest task in the queue. */ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); //thread initialization executor.initialize(); return executor; } }
-
use
Just add the @Async annotation to the annotation of the scheduled task, and explicitly specify the custom thread pool name.
@Component public class ScheduleTest { @Async("scheduleExecutor") @Scheduled(cron = "0/30 * * * * ?") public void task1() { System.out.println("task1 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } @Async("scheduleExecutor") @Scheduled(cron = "0/30 * * * * ?") public void task2() { System.out.println("task2 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } @Async("scheduleExecutor") @Scheduled(cron = "0/50 * * * * ?") public void task3() { System.out.println("task3 start"); System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName()); } }
-