1. Concept and use
@Async is an annotation provided by the Spring framework to mark a method that is executed asynchronously in a separate thread.
This is very useful when dealing with time-consuming operations such as sending emails, calling external APIs, etc.
By using @Async, these operations can be executed in the background without blocking the main thread, thereby improving application performance and responsiveness.
For example, in a web application, a confirmation email may be required when a user submits an order. If using synchronization, the user must wait until the email is sent to complete before the order is submitted successfully. Using @Async, the email sending operation can be performed in the background thread, and the user can get a response to the successful order submission almost immediately.
2. Use
2.1 Enable asynchronous support
First, you need to add the @EnableAsync annotation to the Spring configuration class to enable the asynchronous method execution function. This annotation scans methods with @Async tags and creates independent threads for them to execute.
The sample configuration class is as follows:
@Configuration @EnableAsync public class AppConfig { // Other configurations can be made here, such as bean definitions, etc.}
2.2 Tag asynchronous method
Add @Async annotation on methods that require asynchronous execution. This method should usually return void or Future type.
If void is returned, no results will be returned after the method execution is completed. If Future is returned, the execution result of the asynchronous method can be obtained later.
For example, here is a simple asynchronous method that simulates a time-consuming operation:
@Service public class MyService { @Async public void doSomethingAsync() { try { // Simulation takes time to operate, sleep here for 3 seconds (30000); ("Async method execution is completed"); } catch (InterruptedException e) { (); } } }
2.3 Calling asynchronous methods
This asynchronous method can be called in other components (such as controllers, other service methods, etc.). When called, the method will return immediately, and the actual operation will be executed in the background thread.
For example, in a Spring MVC controller, the above asynchronous method is called:
@RestController public class MyController { @Autowired private MyService myService; @GetMapping("/async") public String asyncEndpoint() { (); return "Async operation started"; } }
2.4 When asynchronous results are required
If an asynchronous method needs to return a result, the return type of the method can be defined as Future. The Future interface is part of the Java concurrency package and is used to represent the result of an asynchronous calculation.
For example, modifying the previous method in MyService is as follows:
@Async public Future<String> doSomethingAsyncWithResult() { try { // Simulation takes time to operate, sleep here for 3 seconds (3000); return new AsyncResult<>("Async method execution result"); } catch (InterruptedException e) { (); return null; } }
Then where this method is called, you can use Future's get method to get the result:
@GetMapping("/async - result") public String asyncResultEndpoint() { try { Future<String> futureResult = (); String result = (); return result; } catch (Exception e) { (); return "Error getting the result"; } }
Note: The get method blocks the current thread until the asynchronous method execution completes and returns the result.
3. Things to note
3.1 Thread pool
By default, Spring uses SimpleAsyncTaskExecutor to execute asynchronous tasks, which creates a new thread for each task. In high concurrency scenarios, this may lead to system resource exhaustion, because creating threads is a relatively resource-consuming operation. Moreover, too many threads will increase the cost of context switching and reduce the overall performance of the system.
For example, suppose there is a web application where a large number of users trigger methods with @Async annotations at the same time. If the thread pool is not configured, a large number of threads may be created, causing the server's CPU and memory resources to be consumed a lot, which will eventually lead to the application's slow response or even crash.
It can be optimized by configuring a custom thread pool.
For example, define a thread pool configuration class:
@Configuration public class ThreadPoolConfig { @Bean("asyncExecutor") public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); (5); (10); (25); ("Async - "); (); return executor; } }
Then specify the thread pool name in the @Async annotation:
@Async("asyncExecutor") public void doSomethingAsync() { // Method content}
Set thread pool parameters reasonably
- CorePoolSize: This is the number of threads that the thread pool has always maintained, and it will not be destroyed even if the thread is idle. It should be set according to the average load of the application. For example, if an application usually needs to process 5 asynchronous tasks at the same time, then the number of core threads can be set to 5.
- MaxPoolSize: It defines the maximum number of threads allowed by a thread pool. When the task queue is full and a new task arrives, the thread pool creates new threads until the maximum number of threads is reached. When setting up, you should consider system resource limitations and task emergencies. If the system resources are limited, the number of threads cannot be increased without limit.
- QueueCapacity: used to store tasks waiting to be executed. When all threads in the thread pool are busy, new tasks will be placed in the task queue. If the queue is full and the maximum number of threads is not reached, a new thread will be created. The size of queue capacity should be determined based on the average processing time of the task and the frequency of the task generation.
Reuse and management of thread pools
- The configured thread pool can reuse threads to improve thread utilization. By reasonably setting the parameters of the thread pool, threads can be switched efficiently between tasks, reducing the overhead of thread creation and destruction. At the same time, you need to pay attention to the life cycle management of thread pools. When the application is closed, the thread pool should be closed correctly to avoid resource leakage.
3.2 Exception handling
Exceptions will not be automatically propagated to the caller, This is an easy issue to be overlooked when using @Async.
When an asynchronous method throws an exception, the exception does not propagate directly to the caller like a synchronous method. This is because the asynchronous method is executed in another thread, and the exception is thrown in this thread. If no special processing is performed, the caller may not know that there is a problem with the asynchronous method.
For example, in a business logic, a method with @Async annotation is called to update the database record. If the asynchronous method throws a SQLException during execution, if this exception is not processed, the caller may continue to perform subsequent operations, believing that the update operation has been successful, resulting in data inconsistency and other problems.
Handle exceptions inside asynchronous method, you can use the try-catch block inside an asynchronous method to catch and handle exceptions. This allows exceptions to be recorded, retryed or remediated within the asynchronous method.
For example:
@Async public void asyncMethod() { try { // Code that may throw exceptions } catch (Exception e) { // Record exception logs ("Exception occurred in an asynchronous method", e); // Retry or other remedial measures can be performed here } }
Configure global asynchronous exception handling mechanism.If you do not want to handle exceptions inside each asynchronous method, you can implement the AsyncUncaughtExceptionHandler interface to configure the global asynchronous exception handling mechanism. This interface has a handleUncaughtException method, which will be called when the asynchronous method throws an uncaught exception.
For example:
@Configuration @EnableAsync public class AppConfig implements AsyncUncaughtExceptionHandler { // Turn on asynchronous supported configurations @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { // Record exception logs ("An uncaught exception occurred asynchronous method, method name: " + (), ex); // Global exception handling strategies can be performed here, such as notifying administrators, etc. } }
3.3 Calling asynchronous methods in the same class
If in a class, a method (Method A) calls another method (Method B) in the same class with the @Async annotation, the @Async annotation may not take effect by default.
This is because Spring's proxy mechanism, when method A directly calls method B, it is not actually called through the proxy object, so asynchronous execution will not be triggered.
For example, in a Service class:
@Service public class MyService { @Async public void asyncMethod() { // Asynchronously executed code } public void anotherMethod() { asyncMethod(); // In this case, @Async may not take effect } }
The solution is to make the call to method B through the injected proxy object. You can inject the current class itself through @Autowired, and then call method B through the proxy object.
For example:
@Service public class MyService { @Autowired private MyService self; @Async public void asyncMethod() { // Asynchronously executed code } public void anotherMethod() { (); // Called through proxy object, @Async takes effect } }
3.4 Circular Dependency
When using @Async, if circular dependencies are involved, it may cause application startup failure or abnormal behavior. Because the asynchronous method's proxy object creation and circular dependency resolution may conflict with each other.
For example, there are two service classes ServiceA and ServiceB, which depend on each other and have methods of @Async annotation.
In this case, it is necessary to carefully check the way of dependency injection and the use of asynchronous methods to avoid problems caused by circular dependencies. The circular dependency problem can be alleviated by adjusting the order of dependency injection, using @Lazy annotation, etc.
Summarize
This is the article about the use and precautions of @Async for Java Spring. For more information about the use and precautions of @Async for Spring, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!