Case:
There are two schedulers, one runs every 3 minutes to get data from the database, and the other runs every REST API to call.
import ; import ; import ; @SpringBootApplication public class YourSpringBootApplication { public static void main(String[] args) { (, args); } } import ; import ; import ; @Component public class FirstScheduler { @Scheduled(fixedRate = 3 * 60 * 1000) // Run every 3 minutes public void getDataFromDB() { // Code to get data from the database } } import ; import ; @Component public class SecondScheduler { @Scheduled(fixedRate = 1 * 60 * 1000) // Run every 1 minute public void consumeRestApi() { // Code to call REST API } }
question:
When the first scheduler runs for more than the expected time, for example, for 10 minutes, the second scheduler will not run until the first scheduler runs.
The problem with the above code is that when one scheduler runs, the second scheduler will be blocked.
Solution:
In order to avoid the second scheduler being blocked by the first scheduler, Spring's asynchronous execution can be used. In doing so, each scheduler will run in its own separate thread to achieve independent work.
Implementation code:
When using Spring's asynchronous execution support, it's best practice to configure a custom thread pool to control the number of threads used to execute asynchronous tasks. Spring uses SimpleAsyncTaskExecutor by default (this may not be appropriate in production because it does not provide more control over thread pools.)
To solve this problem, it is recommended to create a custom thread pool bean in the application. The following is the implementation code:
Step 1: Define a configuration class to create a custom thread pool bean.
import ; import ; import ; import ; @Configuration @EnableAsync public class AsyncConfiguration { @Bean(name = "asyncTaskExecutor") public ThreadPoolTaskExecutor asyncTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); (5); // Set the initial number of threads in the thread pool (10); // Set the maximum number of threads in the thread pool (25); // Set the queue capacity to save suspended tasks ("AsyncTask-"); // Set thread name prefix (); return executor; } }
Step 2: Modify the first scheduler to use this custom thread pool.
import ; import ; import ; @Component public class FirstScheduler { @Async("asyncTaskExecutor") // Specify the custom thread pool bean name @Scheduled(fixedRate = 3 * 60 * 1000) // Run every 3 minutes public void getDataFromDB() { // Code to get data from the database // This method will run asynchronously in a custom thread pool } }
With this configuration, the first scheduler method (getDataFromDB) will run asynchronously in the custom thread pool, while the second scheduler method (consumeRestApi) will run in the thread of the default scheduler.
Adjust corePoolSize, maxPoolSize, and queueCapacity values according to application requirements and available system resources. Thread pool configuration has a relatively important impact on the performance of the application, so these values need to be adjusted appropriately.
To make the second scheduler also execute asynchronously using a custom thread pool, you need to add the taskExecutor attribute in the @Async annotation of the consumptionRestApi method. This ensures that both schedulers are running asynchronously in the same custom thread pool. Here is the updated code:
import ; import ; import ; @Component public class SecondScheduler { @Async("asyncTaskExecutor") // Specify the custom thread pool bean name @Scheduled(fixedRate = 1 * 60 * 1000) // Run every 1 minute public void consumeRestApi() { // Code to call REST API // This method will run asynchronously in a custom thread pool } }
With this setup, both the first scheduler (getDataFromDB) and the second scheduler (consumeRestApi) will run asynchronously in the same custom thread pool. This will allow them to work independently, even if one of the tasks takes longer to complete.
Using custom thread pool
Log an error message when required thread pool size > configured thread pool size:
To log error messages when the required thread pool size exceeds the configured thread pool size, you can use Spring's RejectedExecutionHandler for ThreadPoolTaskExecutor. This handler is called when the task queue of the thread pool is full and the thread pool cannot accept more tasks. This callback can be used to log error messages.
Here is the updated configuration class that contains the RejectedExecutionHandler:
import ; import ; import ; import ; import ; @Configuration @EnableAsync @Sl4J public class AsyncConfiguration { @Bean(name = "asyncTaskExecutor") public ThreadPoolTaskExecutor asyncTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); (5); // Set the initial number of threads in the thread pool (10); // Set the maximum number of threads in the thread pool (25); // Set the queue capacity to save suspended tasks ("AsyncTask-"); // Set thread name prefix // Set RejectedExecutionHandler to log error messages ((Runnable r, ThreadPoolExecutor e) -> { // Log an error message, the queue is full, the task is denied // Customize this message as needed ("Task Denied: The thread pool is full. Please increase the thread pool size."); }); (); return executor; } }
With this configuration, the RejectedExecutionHandler is triggered when the thread pool's queue is full and additional tasks are attempted to submit.
You can customize error messages or take other actions based on the application's needs.
Note that setting the correct thread pool size and queue capacity is critical to the performance and resource utilization of your application.
If the task is continuously rejected because of insufficient queue capacity, it is necessary to increase the thread pool size or adjust the queue capacity.
Summarize:
By configuring a shared custom thread pool, leveraging @Async annotation, and integrating RejectedExecutionHandler, applications can effectively manage and execute multiple scheduler concurrency to ensure that schedulers run independently and the system can gracefully respond to thread pool limitations.
This is the article about the advanced methods of using asynchronous schedulers in SpringBoot. For more related SpringBoot asynchronous schedule content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!