What is “asynchronous invocation”? “Asynchronous invocation” corresponds to “synchronous invocation”. Synchronous invocation means that programs are executed in a defined order. Each line of programs must wait for the completion of the previous line of programs to be executed. Asynchronous invocation refers to the sequential execution of a program without waiting for the result of the asynchronous invocation.

A synchronous invocation

Here is a simple example to get an intuitive understanding of what synchronous calls are:

Define Task class, create three processing functions to simulate three tasks, operation time is randomly selected (within 10 seconds)

@Slf4j
@Component
public class AsyncTasks {

    public static Random random = new Random();

    public void doTaskOne(a) throws Exception {
        log.info("Start on task one.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 1, Time:" + (end - start) + "毫秒");
    }

    public void doTaskTwo(a) throws Exception {
        log.info("Start on task two.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 2, Time:" + (end - start) + "毫秒");
    }

    public void doTaskThree(a) throws Exception {
        log.info("Start on task three.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 3, Time:" + (end - start) + "毫秒"); }}Copy the code

In the unit test case, the Task object is injected and the three functions doTaskOne, doTaskTwo, and doTaskThree are executed in the test case.

@Slf4j
@SpringBootTest
public class Chapter75ApplicationTests {

    @Autowired
    private AsyncTasks asyncTasks;

    @Test
    public void test(a) throws Exception { asyncTasks.doTaskOne(); asyncTasks.doTaskTwo(); asyncTasks.doTaskThree(); }}Copy the code

When you execute the unit test, you see output similar to the following:

[the main] com. Didispace. Chapter75. AsyncTasks: start a task [main] com. Didispace. Chapter75. AsyncTasks: To complete the task, a time-consuming: 4865 milliseconds. [the main] com didispace. Chapter75. AsyncTasks: start the task 2 [main] com. Didispace. Chapter75. AsyncTasks: To complete the task two, time-consuming: 7063 milliseconds. [the main] com didispace. Chapter75. AsyncTasks: start task three [main] com. Didispace. Chapter75. AsyncTasks: Complete Task 3:2076 millisecondsCopy the code

DoTaskOne, doTaskTwo, and doTaskThree have been executed sequentially.

The asynchronous call

Although the above synchronous invocation 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 invocation is relatively poor. Therefore, asynchronous invocation can be considered for concurrent execution.

In Spring Boot, we can simply change a synchronous function to an asynchronous function by using the @async annotation, and change the Task class to the following mode:

@Slf4j
@Component
public class AsyncTasks {

    public static Random random = new Random();

    @Async
    public void doTaskOne(a) throws Exception {
        log.info("Start on task one.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 1, Time:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskTwo(a) throws Exception {
        log.info("Start on task two.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 2, Time:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskThree(a) throws Exception {
        log.info("Start on task three.");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete Task 3, Time:" + (end - start) + "毫秒"); }}Copy the code

In order for the @async annotation to work, you also need to configure @enableAsync in the Spring Boot main program, as follows:

@EnableAsync
@SpringBootApplication
public class Chapter75Application {

    public static void main(String[] args) { SpringApplication.run(Chapter75Application.class, args); }}Copy the code

At this point, you can run unit tests repeatedly, and you may encounter different results, such as:

  • There is no task-related output
  • There are partial task-specific outputs
  • Out-of-order task-related output
  • And the reason is that right nowdoTaskOne,doTaskTwo,doTaskThreeThree functions are already executed asynchronously. After the main program is called asynchronously, the main program does not care whether the execution of these three functions is complete. Because there is no other content to be executed, the program will automatically end, resulting in incomplete or no output task related content.

Note: Do not define @async functions as static, so asynchronous calls do not take effect

An asynchronous callback

In order for doTaskOne, doTaskTwo, and doTaskThree to complete properly, suppose we need to count the total time required for the concurrent execution of the three tasks. We need to record the time and calculate the result after all the three functions are completed.

So how do we determine if the above three asynchronous calls have completed? We need to use CompletableFuture

to return the result of the asynchronous call, like modifying the doTaskOne function as follows:

@Async
public CompletableFuture<String> doTaskOne(a) throws Exception {
    log.info("Start on task one.");
    long start = System.currentTimeMillis();
    Thread.sleep(random.nextInt(10000));
    long end = System.currentTimeMillis();
    log.info("Complete Task 1, Time:" + (end - start) + "毫秒");
    return CompletableFuture.completedFuture("Mission one complete.");
}
Copy the code

After modifying the other two asynchronous functions as described above, let’s modify the test case so that the test does something else after waiting for three asynchronous calls.

@Test
public void test(a) throws Exception {
    long start = System.currentTimeMillis();

    CompletableFuture<String> task1 = asyncTasks.doTaskOne();
    CompletableFuture<String> task2 = asyncTasks.doTaskTwo();
    CompletableFuture<String> task3 = asyncTasks.doTaskThree();

    CompletableFuture.allOf(task1, task2, task3).join();

    long end = System.currentTimeMillis();

    log.info("All tasks completed, total time:" + (end - start) + "毫秒");
}
Copy the code

See what changes we’ve made:

  • Record the start time at the beginning of the test case
  • Returns when three asynchronous functions are calledCompletableFuture<String>Type
  • throughCompletableFuture.allOf(task1, task2, task3).join()Implement blocking until all three asynchronous tasks are finished
  • After all three tasks are complete, calculate the total time required for the concurrent execution of the three tasks based on the end time and start time.

Run the unit test above and see the following results:

[task - 3] com. Didispace. Chapter75. AsyncTasks: start task three [task - 2] com. Didispace. Chapter75. AsyncTasks: Start doing task 2 com [task - 1]. The didispace. Chapter75. AsyncTasks: start a task [task - 2] com. Didispace. Chapter75. AsyncTasks: To complete the task two, time-consuming: 6312 milliseconds [task - 3] com. Didispace. Chapter75. AsyncTasks: To complete the task 3, time-consuming: 8465 milliseconds com [task - 1]. The didispace. Chapter75. AsyncTasks: To complete the task, a time-consuming: 8560 milliseconds [main] C.D.C. hapter75. Chapter75ApplicationTests: mission complete, total time: 8590 millisecondsCopy the code

From: https://blog.didispace.com/spring-boot-learning-2-7-5/