Spring's task scheduling @ Scheduled annotation -- Analysis of task:scheduler and task:executor

Keywords: Java Spring Spring Boot

Spring's task scheduling @ Scheduled annotation -- Analysis of task:scheduler and task:executor

applicationContext is configured as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context  
                        http://www.springframework.org/schema/context/spring-context-3.0.xsd
                        http://www.springframework.org/schema/task 
                        http://www.springframework.org/schema/task/spring-task-4.1.xsd"
                        xmlns:task="http://www.springframework.org/schema/task">

    <context:annotation-config />

    <task:annotation-driven scheduler="myScheduler" executor="myExecutor"/>

    <!-- Scheduling thread pool configuration -->
    <task:scheduler id="myScheduler" pool-size="5"/>
    <!-- Execute thread pool configuration -->
    <task:executor id="myExecutor" pool-size="5"/>

    <context:component-scan base-package="com.zaimeibian" />

</beans>a
package com.zaimeibian.task;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class PrintTask {

    DateFormat df = new SimpleDateFormat("HH:mm:ss");

    // This Async annotation represents that the current task is to be executed asynchronously
    @Async
    @Scheduled(fixedRate = 5000)
    public void printA(){
        System.out.println("A implement " + df.format(new Date()));
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }
        System.out.println("A Printout " + df.format(new Date())+ Thread.currentThread());
    }

    @Scheduled(fixedRate = 5000)
    public void printB(){
        System.out.println("B implement " + df.format(new Date()));
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }
        System.out.println("B Printout " + df.format(new Date())+ Thread.currentThread());
    }

    @Scheduled(fixedRate = 5000)
    public void printC(){
        System.out.println("C implement " + df.format(new Date()));
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }
        System.out.println("C Printout " + df.format(new Date())+ Thread.currentThread());
    }

    // The task of configuring initialDelay is to delay a certain time after the container is started
    @Scheduled(fixedRate = 5000, initialDelay=1000)
    public void printD(){
        System.out.println("D implement " + df.format(new Date()));
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
        }
        System.out.println("D Printout " + df.format(new Date())+ Thread.currentThread());
    }

    // The task of configuring initialDelay is to delay a certain time after the container is started
    @Scheduled(fixedRate = 5000, initialDelay=1000)
    public void printE(){
        System.out.println("E implement " + df.format(new Date()));
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
        }
        System.out.println("E Printout " + df.format(new Date())+ Thread.currentThread());
    }

}

Here, the fixDelay and fixRate parameters represent each task. The former delays the execution of a certain time after the previous task is scheduled. The latter can execute new tasks at regular intervals (but this is related to the parameters of the executor). These two parameters will be described in detail in the following test.
Spring's task scheduling thread pool, i.e

<task:scheduler id="myScheduler" pool-size="5"/>

If it is not configured, the default value is 1, which means that all declared tasks are executed serially. For example, the five tasks A/B/C/D/E in the code can only be executed serially when the default value is 1. Parallelism cannot occur.
You can change the parameter to 1, run, and the output is as follows:

B Execution 12:16:36
B Printout 12:16:46Thread[myScheduler-1,5,main]
A Execution 12:16:46
A Printout 12:16:56Thread[myScheduler-1,5,main]
C Execution 12:16:56
C Printout 12:17:06Thread[myScheduler-1,5,main]
D Execution 12:17:06
D Printout 12:17:36Thread[myScheduler-1,5,main]
E Execution 12:17:36
E Printout 12:18:06Thread[myScheduler-1,5,main]
B Execution 12:18:06
B Printout 12:18:16Thread[myScheduler-1,5,main]
A Execution 12:18:16
A Printout 12:18:26Thread[myScheduler-1,5,main]
C Execution 12:18:26
C Printout 12:18:36Thread[myScheduler-1,5,main]
D Execution 12:18:36
D Printout 12:19:06Thread[myScheduler-1,5,main]
E Execution 12:19:06
E Printout 12:19:36Thread[myScheduler-1,5,main]
B Execution 12:19:36
B Printout 12:19:46Thread[myScheduler-1,5,main]
A Execution 12:19:46
A Printout 12:19:56Thread[myScheduler-1,5,main]

You can see that only myScheduler-1, a scheduling thread, can schedule these five tasks. The task can only be serializer, that is, when the last task is completed, the scheduling thread is released, and then the thread is dispatched to execute the next task.

Then we also change back to the scheduling thread pool, 5 thread pool sizes, and run:

C Execution 12:23:04
A Execution 12:23:04
B Execution 12:23:04
E Execution 12:23:05
D Execution 12:23:05
C Printout 12:23:14Thread[myScheduler-2,5,main]
C Execution 12:23:14
A Printout 12:23:14Thread[myScheduler-3,5,main]
A Execution 12:23:14
B Printout 12:23:14Thread[myScheduler-1,5,main]
B Execution 12:23:14
C Printout 12:23:24Thread[myScheduler-2,5,main]
C Execution 12:23:24
A Printout 12:23:24Thread[myScheduler-3,5,main]
A Execution 12:23:24
B Printout 12:23:24Thread[myScheduler-1,5,main]
B Execution 12:23:24
C Printout 12:23:34Thread[myScheduler-2,5,main]
A Printout 12:23:34Thread[myScheduler-3,5,main]
C Execution 12:23:34
A Execution 12:23:34
B Printout 12:23:34Thread[myScheduler-1,5,main]
B Execution 12:23:34
E Printout 12:23:35Thread[myScheduler-4,5,main]
E Execution 12:23:35
D Printout 12:23:35Thread[myScheduler-5,5,main]
D Execution 12:23:35

It can be seen that if each task has a scheduling thread to process, it is an ideal situation. Each task is parallel, does not interfere with each other, is independent, and is triggered according to its own time. (you can see that 1-5 threads are scheduling their own tasks)
It should also be noted that fixDelay and fixRate look the same. In the scheduling thread of each task, you must wait for the configured time after the last execution before starting the next execution. Does the fixRate parameter not work? Because it doesn't mean that fixRate is executed at a certain interval without waiting for the last task to complete?

Another parameter is introduced here. You can see the @ Async annotation annotated above task A: this annotation represents asynchronous execution. If asynchronous execution is performed, the thread pool will be executed instead of the current thread. Instead, it will be delivered to the task:executor execution thread pool.
Let's run it. For better explanation, we can change the fixRate of A to 2 seconds to see the running results:

B Execution 12:34:44
C Execution 12:34:44
A Execution 12:34:44
D Execution 12:34:45
E Execution 12:34:45
A Execution 12:34:46
A Execution 12:34:48
A Execution 12:34:50
A Execution 12:34:52
B Printout 12:34:54Thread[myScheduler-2,5,main]
B Execution 12:34:54
C Printout 12:34:54Thread[myScheduler-3,5,main]
A Printout 12:34:54Thread[myExecutor-1,5,main]
C Execution 12:34:54
A Execution 12:34:54
A Printout 12:34:56Thread[myExecutor-2,5,main]
A Execution 12:34:56
A Printout 12:34:58Thread[myExecutor-3,5,main]
A Execution 12:34:58
A Printout 12:35:00Thread[myExecutor-4,5,main]
A Execution 12:35:00
A Printout 12:35:02Thread[myExecutor-5,5,main]
A Execution 12:35:02
B Printout 12:35:04Thread[myScheduler-2,5,main]
B Execution 12:35:04
C Printout 12:35:04Thread[myScheduler-3,5,main]
A Printout 12:35:04Thread[myExecutor-1,5,main]
C Execution 12:35:04
A Execution 12:35:04
A Printout 12:35:06Thread[myExecutor-2,5,main]

The threads of task A in are myExecutor-1 to myExecutor-5, indicating that the scheduling thread myScheduler-1 schedules task A, but gives it to the execution thread in myExecutor in the thread pool for specific execution.
Therefore, the thread pool of the task:scheduler parameter is configured to allocate the size of the scheduling thread pool according to the total number of tasks; task:executor is configured to realize multithreading concurrency in the current task if a task is to be executed asynchronously.

Posted by transformationstarts on Mon, 27 Sep 2021 07:07:04 -0700