Spring Two Kinds of Task Scheduling Scheduled and Async Detailed Explanation

  • 2021-11-29 23:46:56
  • OfStack

Directory 1, Two Ways of Spring Scheduling 2, @ Schedule3, @ Async4, Quartz Coming on @ Scheduled and @ Async Use

1. Two modes of Spring scheduling

Spring provides two methods for background tasks, namely:

Scheduling task, @ Schedule Asynchronous task, @ Async

Of course, the use of these two is conditional and needs to be declared in the context of the spring application

< task:annotation-driven/ > Of course, if we are configured based on java, we need to add EnableScheduling and @ EnableAsync to the configuration as follows


@EnableScheduling
@EnableAsync
public class WebAppConfig {
   ....
 } 

In addition, there are third-party libraries that can be called, such as Quartz.

2. @ Schedule

Let's see how @ Schedule is called first


public final static long ONE_DAY = 24 * 60 * 60 * 1000;
public final static long ONE_HOUR = 60 * 60 * 1000;
 
@Scheduled(fixedRate = ONE_DAY)
public void scheduledTask() {
   System.out.println("  I am 1 Every interval 1 It will be executed in days 1 Scheduling tasks for the second time ");
}
 
@Scheduled(fixedDelay = ONE_HOURS)
public void scheduleTask2() {
    System.out.println("  I am 1 After the execution, every other 1 Tasks that will be performed in hours ");
}
 
@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
    // something that should execute periodically
}
 
@Scheduled(cron = "0 0/1 * * * ? ")
public void ScheduledTask3() {
    System.out.println("  I am 1 Every interval 1 Tasks that will be performed in minutes ");
}

What needs attention

For the last one, the task executed at the specified time, the Cron expression is used, and at the same time we see two different faces fixedDelay & fixedRate, the former fixedDelay means to run the program at a specified interval, for example, the program runs at 9:00 tonight, and it will be executed again one hour after running this method, while the latter fixedDelay means that this function will be called every one period of time (we set it here for one day), regardless of whether the method is running or ending when scheduling again. The former requires that the function starts timing after running, which is the difference between the two. There is also a parameter of initialDelay, which is the time to wait before the first call. Here, it means that after being called, it will be postponed for 1 second before execution, which is suitable for 1 special case. When we write these scheduling tasks in serviceImpl class, we also need to write this interface in these excuses of serviceInterface, otherwise but not found in any interface (s) for bean JDK proxy. Either pull the up up up up to to an an an an an an an

3. @ Async

Sometimes we call a special task, which is time-consuming. Importantly, we don't care about the consequences of his return. At this time we need to use this kind of asynchronous task, and let him run after calling, so as not to block the main thread, and we continue to do something else. The code looks like this:


public void AsyncTask(){ 
    @Async
    public void doSomeHeavyBackgroundTask(int sleepTime) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }     
     
    @Async
    public Future<String> doSomeHeavyBackgroundTask() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
     
    public void printLog() {
         System.out.println(" i print a log ,time=" + System.currentTimeMillis());
    } 
}

Let's write a simple test class to test


@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = AsycnTaskConfig.class) // To declare @EnableASync
public class AsyncTaskTest {
    @Autowired
    AsyncTask asyncTask;
    @Test
    public void AsyncTaskTest() throws InterruptedException {
        if (asyncTask != null) {
            asyncTask.doSomeHeavyBackgroundTask(4000);
            asyncTask.printLog();
            Thread.sleep(5000);
        }
    }
}

This feeling is much more convenient than opening one more thread manually. If you don't want to asynchronous, you can directly remove @ Async. In addition, if you want to return a result, you need to add multiple Future < > About this Future, you can write several more articles to introduce it, and introduce FutureTask by the way. If you want to modify the default thread pool configuration of Spring boot, you can implement AsyncConfigurer.

What should be noted:

Compared with @ scheduled, this one can have arguments and return a result, because this one is called by us and the scheduled task is called by spring.

Asynchronous methods can't be called internally, they can only be called externally as above, otherwise they will become synchronous tasks blocking the main thread! I can't believe I jumped into this pit! For example, the following.


public void AsyncTask(){
    public void fakeAsyncTaskTest(){
        doSomeHeavyBackgroundTask(4000);
        printLog();
        // You will find that when you call internally like this, it is actually executed synchronously, not asynchronously! ! 
    }
     
    @Async
    public void doSomeHeavyBackgroundTask(int sleepTime) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     
    public void printLog() {
        System.out.println(" i print a log ");
    } 
}
Another point is not to repeat scanning, which will also lead to asynchronous invalidation. For details, please see spring-async-not-working Issue of this stackoveflow. With regard to exception handling, it is inevitable that exceptions will occur during this asynchronous execution. For this problem, spring provides the following solutions: implementation

AsyncUncaughtExceptionHandler Interface. 
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

After writing our exception handling, we need to configure 1 and tell spring that this exception handling is the exception terminator when we throw an error when running asynchronous tasks


@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Bean
    public AsyncTask asyncBean() {
        return new AsyncTask();
    }
     
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        return executor;
    }
     
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
         return new MyAsyncUncaughtExceptionHandler();
    }
}

4. Quartz debuted

In addition to handling these two, there is also a third-party library integrated with spring called Quartz

I read the introduction of official website, which is also very funny. Now I am used to using maven, gradle and so on to relate these dependencies. He also asked people to download it, but I don't know why. Click on the details- > http://quartz-scheduler.org/documentation/quartz-2.2.x/quick-start

It is estimated that it may be because there is no maintenance. After reading it, the latest version 2.2 is actually Sep, updated in 2013 …

Actually, it is stopped, but Quartz, as a task scheduling framework for enterprise applications, is still a candidate project.

Let's not spread it out here. If you are interested, go to official website to have a look. The whole feeling is not as convenient as spring's own background task, but it is also acceptable, and it can be used only with simple configuration.

Use of @ Scheduled and @ Async

For example, today, I suddenly saw a question about springboot's own scheduler in Zhihu. There is such a paragraph "When using @ Scheduled annotation, if you don't reconfigure the scheduler yourself, you will use the default, which will lead to some scheduling execution problems"; I didn't pay attention to this problem when I used it in the program, so I carefully tested and studied it once, and finally understood some key ideas.

First, you need to know the difference between @ Scheduled and @ Async annotations:

@ Scheduled task scheduling annotation, mainly used to configure timing tasks; The default scheduler thread pool size for springboot is 1.

@ Async task asynchronous execution annotation, mainly used in methods, indicating that the current method will be executed asynchronously with a new thread; springboot default executor thread pool size is 100.

Therefore, if the springboot timer is used, if there are multiple timed tasks and the default scheduler configuration is used, there will be a queuing phenomenon, because only one task can be executed at the same time. At this time, when one task dies, the following timed tasks cannot be effectively executed;

The solution is to customize the scheduler in two ways:

Method 1:


  @Bean
    public TaskScheduler scheduledExecutorService() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("scheduled-thread-");
        // Set the thread pool to close and wait for all tasks to complete before continuing to destroy others Bean
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        // Set the wait time of tasks in the thread pool, and force destruction if they have not been destroyed after this time to ensure that the application can be closed instead of blocked in the end 
        scheduler.setAwaitTerminationSeconds(60);
        // Here, the CallerRunsPolicy Policy, when the thread pool has no processing power, the policy will be directly in the  execute  The rejected task runs in the calling thread of the method; If the executor is closed, the task is discarded 
        scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return scheduler;
    }

Method 2:


@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
 public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
 taskRegistrar.setScheduler(setExecutor());
 }
 
 @Bean(destroyMethod="shutdown")
 public Executor setExecutor(){
 return Executors.newScheduledThreadPool(10); // 10 Three threads to process. 
 }
}

There is a problem in the above-mentioned way of customizing the scheduler: when there are enough spare threads, multitasking will be executed in parallel, but the same timed task will still be executed synchronously (it can be found when the execution time of the timed task is longer than the time interval of each execution);

Use with @ Async annotation, so that a new thread is opened every time a timed task is executed, and it runs asynchronously and non-blocking; The effect of using these two annotations at the same time is equivalent to that @ Scheduled is only responsible for scheduling, while executor specified by @ Async is responsible for task execution, and no longer uses the executor in the scheduler to execute tasks (guessed by the actual test results, no corresponding source logic has been found, which will be supplemented later).

The custom actuator configuration is as follows:


   @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setThreadNamePrefix(threadNamePrefix);
        //  Thread pool's handling strategy for rejecting tasks 
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //  Initialization 
        executor.initialize();
        return executor;
    }

Related articles: