Whether the user-defined thread pool and supplementary asynchronous return have parameters

1. Why do you need asynchronous methods?

Let's start with the conclusion: the rational use of asynchronous methods can make the business interface fly quickly!

The asynchronous method is applicable to the business in which logic and logic can be separated from each other without affecting each other, such as the business composed of generating verification code and sending verification code. In fact, it does not need to wait until the verification code is actually sent successfully to respond to the client. It can turn the time-consuming operation of SMS sending into asynchronous execution and decouple the time-consuming operation and core business;

Similarly, there is the business logic of article reading = query article details + update article reading before responding to the client. In fact, there is no need to wait until the reading is updated before responding to the article details to the client. Viewing articles is the primary logic, while updating article reading is the secondary logic, In addition, even if the update fails, a little data deviation will not affect the user's reading. Therefore, the consistency between the two database operations is weak, which can be optimized by asynchronous events

Therefore, properly adding asynchronous methods to our Service can greatly improve the response speed of the interface and improve the user experience!

Synchronous execution (in the same thread):

Asynchronous execution (start additional threads to execute):

2. Asynchronous method support in springboot

In fact, in SpringBoot, we do not need to create and maintain threads or thread pools to execute asynchronous methods. SpringBoot has provided asynchronous method support annotations

@EnableAsync // When using asynchronous methods, you need to start them in advance (on the startup class or configuration class)
@Async // Methods decorated with async annotations are executed by the spring boot default thread pool (SimpleAsyncTaskExecutor)

For example, use Spring's asynchronous support to query articles and increase reading

Service layer:

@Service
public class ArticleServiceImpl {

    // Query article
    public String selectArticle() {
        // TODO simulation article query operation
        System.out.println("Query task thread"+Thread.currentThread().getName());
        return "Article details";
    }

    // Article reading + 1
    @Async
    public void updateReadCount() {
        // TODO simulation time-consuming operation
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Update task thread"+Thread.currentThread().getName());
    }
}

Controller layer:

@RestController
public class AsyncTestController {

    @Autowired
    private ArticleServiceImpl articleService;

    /**
     * Reading amount after simulated acquisition of articles + 1
     */
    @PostMapping("/article")
    public String getArticle() {
        // Query article
        String article = articleService.selectArticle();
        // Reading amount + 1
        articleService.updateReadCount();
        System.out.println("Article reading and business execution completed");
        return article;
    }

}

Test results: we can feel that the response speed of the interface is greatly improved, and we can see from the log that the two execution tasks are executed in different threads

3. Custom thread pool execution asynchronous method

Spring boot provides us with a thread pool (simpleasynctask executor) by default to execute our asynchronous methods. We can also customize our own thread pool

Step 1: configure a custom thread pool

@EnableAsync // Enable multithreading, which is automatically created when the project starts
@Configuration
public class AsyncConfig {
    @Bean("customExecutor")
    public ThreadPoolTaskExecutor asyncOperationExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Set the number of core threads
        executor.setCorePoolSize(8);
        // Set the maximum number of threads
        executor.setMaxPoolSize(20);
        // Set queue size
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // Set thread active time (seconds)
        executor.setKeepAliveSeconds(60);
        // Set thread name prefix + group name
        executor.setThreadNamePrefix("AsyncOperationThread-");
        executor.setThreadGroupName("AsyncOperationGroup");
        // Close the thread pool after all tasks are completed
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // initialization
        executor.initialize();
        return executor;
    }
}

The second step is to specify the thread pool for execution on the @ Async annotation

// Article reading + 1
@Async("customExecutor")
public void updateReadCount() {
    // TODO simulation time-consuming operation
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Update article reading thread"+Thread.currentThread().getName());
}

5. How to catch exceptions in asynchronous methods (with no return value)

Modify the configuration class with the getAsyncExecutor method and getAsyncUncaughtExceptionHandler method that implement the AsyncConfigurer interface

Custom exception handling class CustomAsyncExceptionHandler

@EnableAsync // Enable multithreading, which is automatically created when the project starts
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // Set the number of core threads
        executor.setCorePoolSize(8);
        // Set the maximum number of threads
        executor.setMaxPoolSize(20);
        // Set queue size
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // Set thread active time (seconds)
        executor.setKeepAliveSeconds(60);
        // Set thread name prefix + group name
        executor.setThreadNamePrefix("AsyncOperationThread-");
        executor.setThreadGroupName("AsyncOperationGroup");
        // Close the thread pool after all tasks are completed
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // initialization
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
 
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("Exception capture---------------------------------");
        System.out.println("Exception message - " + throwable.getMessage());
        System.out.println("Method name - " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value - " + param);
        }
        System.out.println("Exception capture---------------------------------");
    }
     
}

5. How to obtain the return value of (with return value) asynchronous method

Use the Future class and its subclasses to receive the return value of an asynchronous method

be careful:

  • The exception thrown by the asynchronous method without return value will not affect the main business logic of the Controller
  • The exception thrown by the asynchronous method with return value will affect the main business logic of the Controller
// Asynchronous method---------------------------------------------------------------------
@Async
    public CompletableFuture<Integer> updateReadCountHasResult() {
        // TODO simulation time-consuming operation
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Update article reading thread"+Thread.currentThread().getName());
        return CompletableFuture.completedFuture(100 + 1);
    }

// Controller call---------------------------------------------------------------------
@GetMapping("/article")
public String getArticle() throws ExecutionException, InterruptedException {
    // Query article
    String article = articleService.selectArticle();
    // Reading amount + 1
    CompletableFuture<Integer> future = articleService.updateReadCountHasResult();
    int count = 0;
    // Loop waiting for asynchronous request results
    while (true) {
        if(future.isCancelled()) {
            System.out.println("Asynchronous task cancellation");
            break;
        }
        if (future.isDone()) {
            count = future.get();
            System.out.println(count);
            break;
        }
    }
    System.out.println("Article reading and business execution completed");
    return article + count;
}

6. Problems / expansion caused by asynchronous method

  • Asynchronous methods can only be declared in the Service method and called directly by the Controller to take effect. It is strange that asynchronous methods will not take effect when called by the same level Service method?
  • Can asynchronous method + transaction execute smoothly? Perhaps transaction operations should be separated from asynchronous operations. When called by the Controller layer, transaction operations come first and asynchronous operations come second
  • After the asynchronous method fails to execute, it has no impact on the non asynchronous operations in the first half of the Controller. Therefore, the asynchronous method is not 100% reliable in the whole business logic and is not applicable to businesses with strong consistency
  • Or is message oriented middleware more powerful, RabbitMQ, Kafka

Added by roustabout on Sun, 24 Oct 2021 04:17:06 +0300