For image download, domain name resolution and time synchronization, please click Alibaba open source mirror station
First, let's take a look at why asynchronous programming is used in Spring and what problems can it solve?
Why use asynchronous framework? What problems does it solve?
In the daily development of SpringBoot, it is generally called synchronously. However, in practice, there are many scenarios that are very suitable for asynchronous processing, such as registering new users and sending 100 points; Or order successfully, send push message, etc.
Take the use case of registering a new user as an example. Why do you need asynchronous processing?
- The first reason: fault tolerance and robustness. If there is an exception in sending points, the user registration cannot fail because of sending points; Because user registration is the main function and sending points is the secondary function, even if the sending of points is abnormal, the user should be prompted to register successfully, and then compensation will be made for the abnormal points.
- The second reason is to improve performance. For example, it takes 20 milliseconds to register users and 50 milliseconds to send points. If synchronous, it takes 70 milliseconds. If asynchronous, it doesn't need to wait for points, so it takes 20 milliseconds.
Therefore, asynchronous can solve two problems, performance and fault tolerance.
How does SpringBoot implement asynchronous calls?
For asynchronous method calls, the @ Async annotation has been provided since spring 3. We only need to mark this annotation on the method to realize asynchronous calls.
Of course, we also need a configuration class to Enable the asynchronous function through the Enable module driven annotation @ EnableAsync.
Implement asynchronous call
Step 1: create a new configuration class and enable @ Async function support
Use @ EnableAsync to enable asynchronous task support. The @ EnableAsync annotation can be placed directly on the SpringBoot startup class or separately on other configuration classes. Here we choose to use a separate configuration class SyncConfiguration.
@Configuration @EnableAsync public class AsyncConfiguration { }
Step 2: mark the asynchronous call on the method
Add a Component class for business processing, and add @ Async annotation to represent that the method is asynchronous processing.
@Component @Slf4j public class AsyncTask { @SneakyThrows @Async public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); } @SneakyThrows @Async public void doTask2() { long t1 = System.currentTimeMillis(); Thread.sleep(3000); long t2 = System.currentTimeMillis(); log.info("task2 cost {} ms" , t2-t1); } }
Step 3: make asynchronous method calls in the Controller
@RestController @RequestMapping("/async") @Slf4j public class AsyncController { @Autowired private AsyncTask asyncTask; @RequestMapping("/task") public void task() throws InterruptedException { long t1 = System.currentTimeMillis(); asyncTask.doTask1(); asyncTask.doTask2(); Thread.sleep(1000); long t2 = System.currentTimeMillis(); log.info("main cost {} ms", t2-t1); } }
Through access http://localhost:8080/async/task To view the console log:
2021-11-25 15:48:37 [http-nio-8080-exec-8] INFO com.jianzh5.blog.async.AsyncController:26 - main cost 1009 ms 2021-11-25 15:48:38 [task-1] INFO com.jianzh5.blog.async.AsyncTask:22 - task1 cost 2005 ms 2021-11-25 15:48:39 [task-2] INFO com.jianzh5.blog.async.AsyncTask:31 - task2 cost 3005 ms
It can be seen from the log that the main thread does not need to wait for the asynchronous method to complete, which reduces the response time and improves the interface performance.
Through the above three steps, we can happily use asynchronous methods in SpringBoot to improve the performance of our interface. Is it very simple?
However, if you really write this in the actual project development, you will be ruthlessly ridiculed by the old birds. Is that it?
Because the above code ignores the biggest problem, which is to customize the thread pool for the @ Async asynchronous framework.
Why customize the thread pool for @ Async?
Using the @ Async annotation, the SimpleAsyncTaskExecutor thread pool is used by default, which is not a real thread pool.
Thread reuse cannot be realized by using this thread pool. Each call will create a new thread. If threads are constantly created in the system, the system will eventually occupy too much memory and cause OutOfMemoryError error. The key codes are as follows:
public void execute(Runnable task, long startTimeout) { Assert.notNull(task, "Runnable must not be null"); Runnable taskToUse = this.taskDecorator != null ? this.taskDecorator.decorate(task) : task; //Judge whether current limiting is enabled. The default value is No if (this.isThrottleActive() && startTimeout > 0L) { //Perform pre operation to limit current this.concurrencyThrottle.beforeAccess(); this.doExecute(new SimpleAsyncTaskExecutor.ConcurrencyThrottlingRunnable(taskToUse)); } else { //If the flow is not limited, execute the thread task this.doExecute(taskToUse); } } protected void doExecute(Runnable task) { //Keep creating threads Thread thread = this.threadFactory != null ? this.threadFactory.newThread(task) : this.createThread(task); thread.start(); } //Create thread public Thread createThread(Runnable runnable) { //Specify thread name, task-1, task-2 Thread thread = new Thread(this.getThreadGroup(), runnable, this.nextThreadName()); thread.setPriority(this.getThreadPriority()); thread.setDaemon(this.isDaemon()); return thread; }
We can also directly observe from the console log above that the thread names printed each time are incremented by [task-1], [task-2], [task-3], [task-4].
Because of this, when using the @ Async asynchronous framework in Spring, we must customize the thread pool to replace the default SimpleAsyncTaskExecutor.
Spring Provides a variety of thread pools: SimpleAsyncTaskExecutor: It is not a real thread pool. This class does not reuse threads. Each call will create a new thread. SyncTaskExecutor: This class does not implement asynchronous call, but a synchronous operation. Only applicable to places that do not require multithreading ConcurrentTaskExecutor: Executor Is not recommended. If ThreadPoolTaskExecutor Consider using this class only when the requirements are not met ThreadPoolTaskScheduler: have access to cron expression ThreadPoolTaskExecutor : Most commonly used, recommended. Its essence is to java.util.concurrent.ThreadPoolExecutor Packaging of
Implement a custom thread pool for @ Async
@Configuration @EnableAsync public class SyncConfiguration { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //Number of core threads taskExecutor.setCorePoolSize(10); //The thread pool maintains the maximum number of threads. Threads exceeding the number of core threads will be applied only after the buffer queue is full taskExecutor.setMaxPoolSize(100); //Cache queue taskExecutor.setQueueCapacity(50); //Allowed idle time. When the idle time exceeds that of the core thread, the thread other than the core thread will be destroyed after the idle time arrives taskExecutor.setKeepAliveSeconds(200); //Asynchronous method internal thread name taskExecutor.setThreadNamePrefix("async-"); /** * When the task cache queue of the thread pool is full and the number of threads in the thread pool reaches maximumPoolSize, the task rejection policy will be adopted if there are still tasks coming * There are usually four strategies: * ThreadPoolExecutor.AbortPolicy:Discard the task and throw a RejectedExecutionException exception. * ThreadPoolExecutor.DiscardPolicy: It also discards the task without throwing an exception. * ThreadPoolExecutor.DiscardOldestPolicy: Discard the task at the top of the queue, and then try to execute the task again (repeat the process) * ThreadPoolExecutor.CallerRunsPolicy: Retry adding the current task, and automatically call the execute() method repeatedly until it succeeds */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } }
After configuring the custom thread pool, we can boldly use the asynchronous processing capability provided by @ Async.
Multiple thread pool processing
In real Internet project development, for high concurrency requests, the general practice is to isolate the high concurrency interface from a separate thread pool.
Suppose there are two high concurrency interfaces: one is to modify the user information interface and refresh the user redis cache; One is the order placement interface, which sends app push information. Two thread pools are often defined according to the interface characteristics. In this case, we need to distinguish by specifying the thread pool name when using @ Async.
Specify the thread pool name for @ Async
@SneakyThrows @Async("asyncPoolTaskExecutor") public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); }
When there are multiple thread pools in the system, we can also configure a default thread pool. For non default asynchronous tasks, we can specify the thread pool name through @ Async("otherTaskExecutor").
Configure default thread pool
You can modify the configuration class to implement AsyncConfigurer, override the getAsyncExecutor() method, and specify the default thread pool:
@Configuration @EnableAsync @Slf4j public class AsyncConfiguration implements AsyncConfigurer { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //Number of core threads taskExecutor.setCorePoolSize(2); //The thread pool maintains the maximum number of threads. Threads exceeding the number of core threads will be applied only after the buffer queue is full taskExecutor.setMaxPoolSize(10); //Cache queue taskExecutor.setQueueCapacity(50); //Allowed idle time. When the idle time exceeds that of the core thread, the thread other than the core thread will be destroyed after the idle time arrives taskExecutor.setKeepAliveSeconds(200); //Asynchronous method internal thread name taskExecutor.setThreadNamePrefix("async-"); /** * When the task cache queue of the thread pool is full and the number of threads in the thread pool reaches maximumPoolSize, the task rejection policy will be adopted if there are still tasks coming * There are usually four strategies: * ThreadPoolExecutor.AbortPolicy:Discard the task and throw a RejectedExecutionException exception. * ThreadPoolExecutor.DiscardPolicy: It also discards the task without throwing an exception. * ThreadPoolExecutor.DiscardOldestPolicy: Discard the task at the top of the queue, and then try to execute the task again (repeat the process) * ThreadPoolExecutor.CallerRunsPolicy: Retry adding the current task, and automatically call the execute() method repeatedly until it succeeds */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } /** * Specifies the default thread pool */ @Override public Executor getAsyncExecutor() { return executor(); } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> log.error("Thread pool execution task sending unknown error,Execution method:{}",method.getName(),ex); } }
As follows, doTask1() uses the default thread pool asyncPoolTaskExecutor, and doTask2() uses the thread pool otherTaskExecutor, which is very flexible.
@Async public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); } @SneakyThrows @Async("otherTaskExecutor") public void doTask2() { long t1 = System.currentTimeMillis(); Thread.sleep(3000); long t2 = System.currentTimeMillis(); log.info("task2 cost {} ms" , t2-t1); }
Summary
@Async asynchronous method is often used in daily development. We should master it well and strive to become an old bird as soon as possible!!!
This article is from: JAVA Daily Bulletin