It is said that you can master Spring Boot integration, timed tasks and asynchronous calls in ten minutes?

Posted by scialom on Fri, 11 Feb 2022 00:07:07 +0100

1. Scheduled tasks

In project development, we often need timed tasks to help us do some content, such as sending SMS / station information regularly, data summary and statistics, business monitoring, etc., so we need to use our timed tasks. It is very simple to write timed tasks in Spring Boot. The following is an example of how to create timed tasks in Spring Boot

1.1 @Scheduled - "xedRate" mode

1.1.1 pom configuration

Just introduce the Spring Boot Starter jar package. The timing method has been built in the Spring Boot Starter package

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
Copy code

1.1.2 add notes

Add * * @ enableshcheduling * * annotation to the main class of Spring Boot to enable the configuration of scheduled tasks

package com;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class ScheduleTaskApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduleTaskApplication.class, args);
    }

}

Copy code

1.1.3 create test class

package com.task;

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

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

//Scheduled task
@Component
public class SchedulerTask {
    private static final SimpleDateFormat f=new SimpleDateFormat("HH:mm:ss");

    @Scheduled(fixedRate = 5000)//Once every 5 seconds
    public void processFixedRate(){
        System.out.println("processFixedRate Method to start the scheduled task: the current time is"+f.format(new Date()));
    }
}
Copy code

1.1.4 parameter description

In the above introductory example, the @ Scheduled(fixedRate = 5000) annotation is used to define the tasks to be executed every 5 seconds. The use of @ Scheduled can be summarized as follows:

  1. @Scheduled(fixedRate = 5000): execute 5 seconds after the last execution time point
  2. @Scheduled(fixedDelay = 5000): execute 5 seconds after the last execution
  3. @Scheduled(initialDelay=1000, fixedRate=5000): execute after the first delay of 1 second, and then execute every 5 seconds according to the rule of "fixedrate"

1.1.5 operation test

1.2 @ scheduled cron mode

You can also implement scheduled tasks in another way, just modify the test class

1.2.1 modify test class

package com.task;

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

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

//Scheduled task
@Component
public class SchedulerTask {
    private static final SimpleDateFormat f=new SimpleDateFormat("HH:mm:ss");

    @Scheduled(cron = "*/5 * * * * *")
    public void processFixedRate(){
        System.out.println("processFixedRate Method to start the scheduled task: the current time is"+f.format(new Date()));
    }
}

Copy code

1.2.2 testing

1.2.3 parameter description

cron has seven digits in total, and the last digit is year. Only six digits need to be set in the Spring Boot timing scheme

  1. The first digit indicates seconds, and the value is 0 ~ 59;
  2. The second digit indicates score, with a value of 0 ~ 59;
  3. The third digit indicates the hour, and the value is 0 ~ 23;
  4. Fourth digit, date / day, value 1 ~ 31;
  5. The fifth digit is the date month, with a value of 1 ~ 12;
  6. Sixth place, week, value 1 ~ 7, Monday, Tuesday, Note: 1 means Sunday and 2 means Monday;
  7. The seventh digit is the year. It can be left blank. The value is 1970 ~ 2099

There are also some special symbols in cron, which have the following meanings:

  1. (*) asterisk, can be understood as the meaning of every, every second, every minute, every day, every month, every year (?) Question mark: the question mark can only appear in the two positions of date and week, indicating that the value of this position is uncertain (-) minus sign, indicating a range. If "10 ~ 12" is used in the hour field, it means from 10 to 12 o'clock, i.e. 10, 11 and 12
  2. (,) comma indicates a list value. If "1, 2, 4" is used in the week field, it indicates Monday, Tuesday and Thursday
  3. (/) slash, such as x/y, where x is the start value and Y is the step length. For example, in the first bit (second), 0 / 15 starts from 0 seconds and is executed every 15 seconds.

Here are some common examples of 0 *: Execute at 1 a.m. every day; 0 5 1 * * ?: At 1:05 a.m. every day;

2. Asynchronous call

2.1 synchronous call

Synchronous call means that the program is executed in sequence according to the defined order. Each line of program must wait for the execution of the previous line of program before it can be executed

2.1.1 define a Task class

Create three processing functions to simulate three operations of executing tasks respectively, and the operation consumption time is taken randomly (10 seconds)

package com.task;

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

import java.util.Random;

//Synchronous call
@Component
public class AsyncTask {

    public static Random random = new Random();

    
    public void testTask1() throws Exception{
        System.out.println("Open Task 1");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 1"+(endtime-starttime)+"millisecond");
    }
   
    public void testTask2() throws Exception{
        System.out.println("Open Task 2");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 2"+(endtime-starttime)+"millisecond");
    }
    
    public void testTask3() throws Exception{
        System.out.println("Open Task 3");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 3"+(endtime-starttime)+"millisecond");

    }
}

Copy code

2.1.2 create test class

package com;

import com.task.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ScheduleTaskApplicationTests {

    @Test
    void contextLoads() {
    }

    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void testTask() throws Exception{
        asyncTask.testTask1();
        asyncTask.testTask2();
        asyncTask.testTask3();
    }

}
Copy code

2.1.3 testing

The execution of task 1, task 2 and task 3 is completed in sequence. In other words, the execution of testTask1, testTask2 and testTask3 is completed in sequence.

2.2 asynchronous call

Although the above synchronous calls have successfully executed the three tasks, it can be seen that the execution time is relatively long. If there is no dependency between the three tasks and they can be executed concurrently, the execution efficiency of synchronous calls is relatively poor. It can be considered to execute concurrently through asynchronous calls. Asynchronous calls refer to the sequential execution of programs, Execute the following program without waiting for the return result of the asynchronous call statement.

In Spring Boot, we can simply change the original synchronous function into asynchronous function by using @ Async annotation

2.2.1 modify Task class

package com.task;

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

import java.util.Random;

//Synchronous call
@Component
public class AsyncTask {

    public static Random random = new Random();

    @Async
    public void testTask1() throws Exception{
        System.out.println("Open Task 1");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 1"+(endtime-starttime)+"millisecond");
    }
    @Async
    public void testTask2() throws Exception{
        System.out.println("Open Task 2");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 2"+(endtime-starttime)+"millisecond");
    }
    @Async
    public void testTask3() throws Exception{
        System.out.println("Open Task 3");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 3"+(endtime-starttime)+"millisecond");

    }
}
Copy code

2.2.2 modify SpringbootAsyncApplication

In order for the @ Async annotation to take effect, you also need to configure @ EnableAsync in the main program of Spring Boot

package com;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAsync
public class ScheduleTaskApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduleTaskApplication.class, args);
    }

}

Copy code

At this time, you can perform unit tests repeatedly, and you may encounter various results:

  • There are no task related outputs
  • There are some task related outputs
  • Out of order task related output

The reason is that testTask1, testTask2 and testTask3 are executed asynchronously. After the main program is called asynchronously, the main program does not care whether the three functions have been executed. Because there is no other content to be executed, the program ends automatically, resulting in incompleteness or no output of task related content

2.3 asynchronous call result return

In order to make testTask1, testTask2 and testTask3 , complete normally, suppose we need to count the total time taken for the concurrent execution of the three tasks, which requires us to wait until the transfer of the above three functions is completed, record the time and calculate the results. How can we judge whether the above three asynchronous calls have been completed? We need to use Future to return the results of asynchronous calls

2.3.1 transform AsyncTask

package com.task;

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

import java.util.Random;
import java.util.concurrent.Future;

//Synchronous call
@Component
public class AsyncTask {

    public static Random random = new Random();

    @Async
    public Future<String> testTask1() throws Exception{
        System.out.println("Open Task 1");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 1"+(endtime-starttime)+"millisecond");
        return new AsyncResult<>("Once the task is completed");
    }
    @Async
    public Future<String> testTask2() throws Exception{
        System.out.println("Open Task 2");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 2"+(endtime-starttime)+"millisecond");
        return new AsyncResult<>("Task 2 completed");
    }
    @Async
    public Future<String> testTask3() throws Exception{
        System.out.println("Open Task 3");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 3"+(endtime-starttime)+"millisecond");
        return new AsyncResult<>("Task 3 completed");
    }
}
Copy code

2.3.2 transformation test

package com;

import com.task.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.Future;

@SpringBootTest
class ScheduleTaskApplicationTests {

    @Test
    void contextLoads() {
    }

    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void testTask() throws Exception{
//        asyncTask.testTask1();
//        asyncTask.testTask2();
//        asyncTask.testTask3();
        Future<String> taskOne = asyncTask.testTask1();
        Future<String> taskTwo = asyncTask.testTask2();
        Future<String> taskThree = asyncTask.testTask3();

        while (true){
            if (taskOne.isDone()&&taskTwo.isDone()&&taskThree.isDone()){
                break;
            }
            Thread.sleep(10000);
        }

    }

}
Copy code

2.3.3 testing

2.3.4 summary

  • Record the start time at the beginning of the test case
  • When three asynchronous functions are called, the result object of type Future is returned
  • After calling the three asynchronous functions, start a loop to judge whether the three asynchronous functions have ended according to the returned Future object. If all ends, the cycle ends; If not, wait 1 second before judging.
  • After jumping out of the loop, calculate the total time spent on the concurrent execution of the three tasks according to the end time start time

2.4 asynchronously calling custom thread pool

Open the asynchronous annotation @ EnableAsync method and add @ Async. The default implementation of SimpleAsyncTaskExecutor is not a real thread pool. This class does not reuse threads, and a new thread will be created every time it is called

2.4.1 custom thread pool

package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootAsyncApplication.class, args);
    }

    @Bean("myTaskExecutor")
    public Executor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);//Number of core threads, the number of threads initialized when the thread pool is created
        executor.setMaxPoolSize(15);//The maximum number of threads. Threads exceeding the number of core threads will be applied only after the buffer queue is full
        executor.setQueueCapacity(200);//Buffer queue, which is used to buffer the queue for executing tasks
        executor.setKeepAliveSeconds(60);//Threads that exceed the number of core threads will be destroyed after idle time
        executor.setThreadNamePrefix("myTask-");//After setting, it is convenient for us to locate the thread pool where the processing task is located
        executor.setWaitForTasksToCompleteOnShutdown(true);//It is used to set when the thread pool is closed and wait until all tasks are completed
 Continue to destroy other Bean
        executor.setAwaitTerminationSeconds(60);//This method is used to set the waiting time of tasks in the thread pool. If it exceeds this time, it has not been set
 If there is destruction, it will be forcibly destroyed to ensure that the application can be closed rather than blocked in the end.
        //Processing strategy of the thread pool for rejecting tasks: the CallerRunsPolicy policy is adopted here. When the thread pool has no processing capacity, this policy will be directly used in the 
execute Method to run the rejected task in the calling thread of the method; If the executor is closed, the task is discarded
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}
Copy code

2.4.2 transform AsyncTask

Add a custom thread pool name after @ Async

package com.task;

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

import java.util.Random;
import java.util.concurrent.Future;

//Synchronous call
@Component
public class AsyncTask {

    public static Random random = new Random();

    @Async("myTaskExecutor")
    public Future<String> testTask1() throws Exception{
        System.out.println("Open Task 1");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 1"+(endtime-starttime)+"millisecond");
        return new AsyncResult<>("Once the task is completed");
    }
    @Async("myTaskExecutor")
    public Future<String> testTask2() throws Exception{
        System.out.println("Open Task 2");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 2"+(endtime-starttime)+"millisecond");
        return new AsyncResult<>("Task 2 completed");
    }
    @Async("myTaskExecutor")
    public Future<String> testTask3() throws Exception{
        System.out.println("Open Task 3");
        long starttime = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long endtime = System.currentTimeMillis();
        System.out.println("Time spent completing task 3"+(endtime-starttime)+"millisecond");
        return new AsyncResult<>("Task 3 completed");
    }
}

Topics: Java Spring Spring Boot Back-end Programmer