Spring Boot series: Spring Boot asynchronous call @ Async

Keywords: Java Spring SpringBoot github

In actual development, sometimes in order to process the request and respond in time, we may execute multiple tasks at the same time, or process the main task first, that is, asynchronous call. There are many implementations of asynchronous call, such as multithreading, timed task, message queue, etc,

In this chapter, we will talk about @ Async asynchronous method calls.

I. use demonstration of @ Async

@Async is Spring's built-in annotation, which is used to handle asynchronous tasks. It is also applicable in Spring boot. In Spring boot projects, except for the starter of boot itself, there is no need to introduce additional dependency.

To use @ Async, you need to add @ EnableAsync active declaration to the startup class to enable asynchronous methods.

@EnableAsync
@SpringBootApplication
public class SpringbootApplication {

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

}

Now there are three tasks to deal with, which correspond to the taskOne, taskTwo and taskThree methods of AsyncTask class. Here, we do the thread sleep to simulate the actual operation.

@Slf4j
@Component
public class AsyncTask {

    private Random random = new Random();
    
    public void taskOne() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Completion time of task one{}second", (end - start)/1000f);
    }
    
    public void taskTwo() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Task 2 completion time{}second", (end - start)/1000f);
    }
    
    public void taskThree() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Completion time of task 3{}second", (end - start)/1000f);
    }

}

Then write the test class. Since @ Async annotation can take effect only after the Spring container is started, the test class is put under the test package of SpringBoot and uses SpringBootTest.

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootApplication.class)
public class AsyncTaskTest {

    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void doAsyncTasks(){
        try {
            long start = System.currentTimeMillis();
            asyncTask.taskOne();
            asyncTask.taskTwo();
            asyncTask.taskThree();
            Thread.sleep(5000);
            long end = System.currentTimeMillis();
            log.info("Main program execution time{}second", (end - start)/1000f);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

When running the test method, you can see that task one, two, three is executed in sequence, and the main program is finished at last, which is the same as our expectation, because we do not have any additional processing, they are ordinary methods, which are executed in sequence according to the coding sequence.

If we want to execute the task concurrently, we only need to use @ Async annotation on the task method. Note that the method decorated by @ Async should not be defined as static type, so the asynchronous call will not take effect.

@Slf4j
@Component
public class AsyncTask {

    private Random random = new Random();

    //@The function decorated by Async should not be defined as static type, so the asynchronous call will not take effect
    @Async
    public void taskOne() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Completion time of task one{}second", (end - start)/1000f);
    }

    @Async
    public void taskTwo() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Task 2 completion time{}second", (end - start)/1000f);
    }

    @Async
    public void taskThree() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Completion time of task 3{}second", (end - start)/1000f);
    }

}

Then we run the test class. At this time, the output may be various. Any task may be completed first, or some methods may not be output because the main program is closed.

II. Future obtains asynchronous execution results

The above shows @ Async, but sometimes in addition to concurrent task scheduling, we need to get the return value of the task, and finish the main task after the multitasking is completed. What should we do at this time?

In multithreading, you can get the return value through Callable and Future, which is similar here. We use Future to return the execution result of the method. AsyncResult is an implementation class of Future.

@Slf4j
@Component
public class FutureTask {

    private Random random = new Random();

    //@The function decorated by Async should not be defined as static type, so the asynchronous call will not take effect
    @Async
    public Future<String> taskOne() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Completion time of task one{}second", (end - start)/1000f);
        return new AsyncResult <>("Task one Ok");
    }

    @Async
    public Future<String> taskTwo() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Task 2 completion time{}second", (end - start)/1000f);
        return new AsyncResult <>("Task two OK");
    }

    @Async
    public Future<String> taskThree() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Completion time of task 3{}second", (end - start)/1000f);
        return new AsyncResult <>("Task three Ok");
    }

}

In AsyncResult:

  • The isDone() method can be used to determine whether the asynchronous method is completed. If the task is completed, it returns true
  • The get() method can be used to get the result returned after the task is executed
  • cancel(boolean mayInterruptIfRunning) can be used to cancel a task. The parameter mayInterruptIfRunning indicates whether to allow canceling a task that is being executed but has not been completed. If true is set, the task in execution can be cancelled
  • The isCancelled() method indicates whether the task has been cancelled successfully. If it has been cancelled successfully before the task completes normally, it returns true
  • get(long timeout, TimeUnit unit) is used to get the execution result. If the result is not obtained within the specified time, null will be returned directly
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootApplication.class)
public class AsyncTaskTest {

    @Autowired
    private FutureTask futureTask;

    @Test
    public void doFutureTasks(){
        try {
            long start = System.currentTimeMillis();
            Future <String> future1 = futureTask.taskOne();
            Future <String> future2 = futureTask.taskTwo();
            Future <String> future3 = futureTask.taskThree();
            //Execute the main program after three tasks are completed
            do {
                Thread.sleep(100);
            } while (future1.isDone() && future2.isDone() && future3.isDone());
            log.info("Get the return value of the asynchronous method:{}", future1.get());
            Thread.sleep(5000);
            long end = System.currentTimeMillis();
            log.info("Main program execution time{}second", (end - start)/1000f);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

Run the test class, we can see that task 123 is executed asynchronously, the main task is finished at last, and the return information of the task can be obtained.

Source address: https://github.com/imyanger/springboot-project/tree/master/p23-springboot-async

Posted by cdhogan on Wed, 06 Nov 2019 12:50:51 -0800