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