preface

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 before they can be executed. Asynchronous invocation refers to the sequential execution of a program without waiting for the result of the asynchronous invocation.

This series of articles

  1. Spring Boot 2.0 series 1 – Build Docker images using Gradle
  2. Spring Boot 2.0 Series ii – Global exception handling and testing
  3. Spring Boot 2.0 series (3) – Using @async to make asynchronous calls
  4. Spring Boot 2.0 series 4 – Use WebAsyncTask to handle asynchronous tasks
  5. Spring Boot 2.0 series 5 – Listener, Servlet, Filter, and Interceptor
  6. Spring Boot 2.0 series (vi) – Several implementations of single machine timing tasks

The body of the

1. Environment preparation

Create a Gradle project spring-boot-Async-Task using Spring Initializer. Add dependencies when creating gradle project spring-boot-Async-Task. The resulting initial build.gradle looks like this:

buildscript {
    ext {
        springBootVersion = '. The 2.0.3 RELEASE '
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'io.ostenant.springboot.sample'
version = '0.0.1 - the SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')}Copy the code

Configure the @enableAsync annotation on the Spring Boot entry class to enable asynchronous processing.

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

Create a task abstract class AbstractTask and configure three task methods: doTaskOne(), doTaskTwo(), and doTaskThree().

public abstract class AbstractTask {
    private static Random random = new Random();

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

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

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

2. Synchronize the call

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

  • defineTaskClass, inheritance,AbstractTask, the three processing functions simulate the operations of three tasks respectively, and the operation consumption time is randomly selected (10Seconds).
@Component
public class Task extends AbstractTask {}Copy the code
  • inUnit testingUse case, injectionTaskObject and execute in the test casedoTaskOne().doTaskTwo().doTaskThree()Three ways.
@RunWith(SpringRunner.class)
@SpringBootTest
public class TaskTest {
    @Autowired
    private Task task;

    @Test
    public void testSyncTasks(a) throws Exception { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); }}Copy the code
  • When you execute the unit test, you see output similar to the following:
Start task 1 Finish task 1:4059 milliseconds Start task 2 Finish task 2:6316 milliseconds Start task 3 Finish task 3:1,973 millisecondsCopy the code

DoTaskOne (), doTaskTwo(), and doTaskThree() are executed in sequence.

3. Asynchronous invocation

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.

  • createAsyncTaskClass, configured separately on methods@AsyncAnnotate the originalSynchronized methodsintoAsynchronous methods.
@Component
public class AsyncTask extends AbstractTask {
    @Async
    public void doTaskOne(a) throws Exception {
        super.doTaskOne();
    }

    @Async
    public void doTaskTwo(a) throws Exception {
        super.doTaskTwo();
    }

    @Async
    public void doTaskThree(a) throws Exception {
        super.doTaskThree(); }}Copy the code
  • inUnit testingUse case, injectionAsyncTaskObject and execute in the test casedoTaskOne().doTaskTwo().doTaskThree()Three ways.
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncTaskTest {
    @Autowired
    private AsyncTask task;

    @Test
    public void testAsyncTasks(a) throws Exception { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); }}Copy the code
  • When you execute the unit test, you see output similar to the following:
Let's do task three let's do task one let's do task twoCopy the code

If you run unit tests repeatedly, you may encounter different results, such as:

  1. There is no task-related output
  2. There are partial task-specific outputs
  3. Out-of-order task-related output

The reason is that doTaskOne(), doTaskTwo(), and doTaskThree() are currently 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: Functions decorated by @async should not be defined as static, so asynchronous calls do not take effect.

4. Asynchronous callback

In order for doTaskOne(), doTaskTwo(), and doTaskThree() to finish properly, suppose we need to count the total time it takes to execute the three tasks simultaneously. We need to record the time and calculate the result after all three functions have been used.

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

to return the result of the asynchronous call.

  • createAsyncCallBackTaskClasses, a statementdoTaskOneCallback().doTaskTwoCallback().doTaskThreeCallback()Three methods, the original three methods for packaging.
@Component
public class AsyncCallBackTask extends AbstractTask {
    @Async
    public Future<String> doTaskOneCallback(a) throws Exception {
        super.doTaskOne();
        return new AsyncResult<>("Mission one complete.");
    }

    @Async
    public Future<String> doTaskTwoCallback(a) throws Exception {
        super.doTaskTwo();
        return new AsyncResult<>("Mission two completed.");
    }

    @Async
    public Future<String> doTaskThreeCallback(a) throws Exception {
        super.doTaskThree();
        return new AsyncResult<>("Mission three completed."); }}Copy the code
  • inUnit testingUse case, injectionAsyncCallBackTaskObject and execute in the test casedoTaskOneCallback().doTaskTwoCallback().doTaskThreeCallback()Three ways. Cycle callFutureisDone()Method waits for threeConcurrent tasksWhen the execution is complete, record the final execution time.
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncCallBackTaskTest {
    @Autowired
    private AsyncCallBackTask task;

    @Test
    public void testAsyncCallbackTask(a) throws Exception {
        long start = currentTimeMillis();
        Future<String> task1 = task.doTaskOneCallback();
        Future<String> task2 = task.doTaskTwoCallback();
        Future<String> task3 = task.doTaskThreeCallback();

        // All three tasks are completed
        while(! task1.isDone() || ! task2.isDone() || ! task3.isDone()) { sleep(1000);
        }

        long end = currentTimeMillis();
        out.println("All tasks completed, total time:" + (end - start) + "毫秒"); }}Copy the code

See what changes have been made:

  • Record the start time at the beginning of the test case;
  • Returns a result object of type Future when three asynchronous functions are called;
  • After three asynchronous functions are called, a loop is opened to determine whether all three asynchronous functions are finished based on the Future object returned. If both end, the loop ends; If not, wait 1 second before judging.
  • After exiting the loop, calculate the total time required for the three tasks to be executed concurrently based on the end time and start time.

Run the unit test above and see the following results:

Start Task 1 Start Task 3 Start Task 2 Complete Task 2:4882 milliseconds Complete Task 3:6484 milliseconds Complete Task 1:8748 milliseconds Complete task 1:9043 millisecondsCopy the code

It can be seen that through asynchronous invocation, task one, task two and task three are executed concurrently, which effectively reduces the total running time of the program.

5. Define a thread pool

In the above operation, create a thread pool configuration class TaskConfiguration and configure a task thread pool object taskExecutor.

@Configuration
public class TaskConfiguration {
    @Bean("taskExecutor")
    public Executor taskExecutor(a) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setRejectedExecutionHandler(new CallerRunsPolicy());
        returnexecutor; }}Copy the code

We created a thread pool using ThreadPoolTaskExecutor and set the following parameters:

Thread pool properties The role of attributes Setting the initial value
Core threads The number of threads initialized when the thread pool was created 10
Maximum number of threads The maximum number of threads in the thread pool, and only after the buffer queue is full will more threads than the number of core threads be applied 20
Buffer queue A queue used to buffer the execution of tasks 200
The idle time allowed for threads Threads beyond the core thread are destroyed when idle time expires 60 seconds
The prefix of the thread pool name Can be used to locate the thread pool where the processing task resides taskExecutor-
The processing policy of the thread pool for rejected tasks In this case, the CallerRunsPolicy policy is used. When the thread pool has no processing capacity, this policy will directly run the rejected task in the execute method calling thread. If the executor is closed, the task is discarded CallerRunsPolicy
  • createAsyncExecutorTaskClass, three task configuration andAsyncTaskIt’s the same, but it’s different@AsyncAnnotations need to specify the previous configurationThe name of the thread pool taskExecutor.
@Component
public class AsyncExecutorTask extends AbstractTask {
    @Async("taskExecutor")
    public void doTaskOne(a) throws Exception {
        super.doTaskOne();
        out.println("Task 1, current thread:" + currentThread().getName());
    }

    @Async("taskExecutor")
    public void doTaskTwo(a) throws Exception {
        super.doTaskTwo();
        out.println("Task 2, current thread:" + currentThread().getName());
    }

    @Async("taskExecutor")
    public void doTaskThree(a) throws Exception {
        super.doTaskThree();
        out.println("Task 3, current thread:"+ currentThread().getName()); }}Copy the code
  • inUnit testingUse case, injectionAsyncExecutorTaskObject and execute in the test casedoTaskOne().doTaskTwo().doTaskThree()Three ways.
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncExecutorTaskTest {
    @Autowired
    private AsyncExecutorTask task;

    @Test
    public void testAsyncExecutorTask(a) throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();

        sleep(30 * 1000L); }}Copy the code

Run the unit test above and see the following results:

Start Task 1 Start Task 3 Start Task 2 Finish Task 2, duration: 3905 ms Task 2, current thread: TaskExecutor-2 Complete Task 1, duration: 6184 ms Task 1, current thread: TaskExecutor-1 Complete Task 3, Duration: Task 3, current thread: taskExecutor-3Copy the code

Execute the unit test above and observe that the prefix of the thread pool name of the task thread pool is printed, indicating that the thread pool successfully executed the asynchronous task!

6. Gracefully close the thread pool

Because the asynchronous task is still running when the application is shut down, objects such as the database connection pool are destroyed, and an error occurs when the database is operated on in the asynchronous task.

Solution as follows, resetting the thread pool configuration object, the new thread pool setWaitForTasksToCompleteOnShutdown () and setAwaitTerminationSeconds () function:

@Bean("taskExecutor")
public Executor taskExecutor(a) {
    ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
    executor.setPoolSize(20);
    executor.setThreadNamePrefix("taskExecutor-");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setAwaitTerminationSeconds(60);
    return executor;
}
Copy the code
  • setWaitForTasksToCompleteOnShutdown(true): This method is used to set up the thread pool shutdown to wait for all tasks to complete before continuing to destroy other beans, so that the destruction of these asynchronous tasks precedes the destruction of database connection pool objects.

  • SetAwaitTerminationSeconds (60) : this method is used to set the thread pool task waiting time, if more than this time haven’t destroy force to destroy, to ensure that the application of the last to be closed, and not blocked.

summary

This article showed you how to use the @async annotation in Spring Boot to configure asynchronous tasks, asynchronous callback tasks, including in conjunction with the use of task thread pools, and how to properly and gracefully turn off task thread pools.


Welcome to pay attention to the technical public number: Zero one Technology Stack

This account will continue to share learning materials and articles on back-end technologies, including virtual machine basics, multithreaded programming, high-performance frameworks, asynchronous, caching and messaging middleware, distributed and microservices, architecture learning and progression.