In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article is about how SpringBoot implements asynchronous processing. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.
Asynchronous request and synchronous request
Let's first use a diagram to distinguish between asynchronous and synchronous requests:
There are three roles in the figure above: the client, the Web container, and the business processing thread.
Client requests to the Web container in both processes are synchronous. Because they are in a blocking waiting state when requesting the client, they are not processed asynchronously.
In the Web container section, the first process takes the form of synchronous requests and the second process takes the form of asynchronous callbacks.
Through asynchronous processing, we can first release the threads and related resources allocated by the container to the request, reducing the burden on the system, thus increasing the throughput of the server to the client request. However, when the number of concurrent requests is large, it is usually solved by load balancing rather than asynchronism.
Async in Servlet3.0
Prior to Servlet 3.0, Servlet handled requests in a Thread-Per-Request manner, meaning that each Http request was processed by a thread from beginning to end. When it comes to time-consuming operations, performance problems are more obvious.
Asynchronous processing of requests is provided in Servlet 3.0. You can first release the container allocated to the requested thread and related resources to reduce the burden on the system, thereby increasing the throughput of the service.
The async of Servlet 3.0 is done through the AsyncContext object, which can be passed from the current thread to another thread and returned to the original thread. The new thread can return the result directly to the client after processing the business.
The AsyncContext object can be obtained from HttpServletRequest:
@ RequestMapping ("/ async") public void async (HttpServletRequest request) {AsyncContext asyncContext = request.getAsyncContext ();}
Features such as obtaining ServletRequest, ServletResponse and adding addListener are provided in AsyncContext:
Public interface AsyncContext {ServletRequest getRequest (); ServletResponse getResponse (); void addListener (AsyncListener var1); void setTimeout (long var1); / / omit other methods}
You can not only get information such as Request and Response through AsyncContext, but also set the asynchronous processing timeout. In general, the timeout (in milliseconds) needs to be set, otherwise waiting infinitely would be the same as synchronization.
Through AsyncContext's addListener, you can also add listening events to handle event callbacks such as start, completion, exception, timeout, and so on, of asynchronous threads.
The source code of the parameter AsyncListener of the addListener method is as follows:
Public interface AsyncListener extends EventListener {/ / call void onComplete (AsyncEvent var1) throws IOException; / / Asynchronous thread executes timeout call void onTimeout (AsyncEvent var1) throws IOException; / / call void onError (AsyncEvent var1) throws IOException; / / Asynchronous thread calls void onStartAsync (AsyncEvent var1) throws IOException; at the beginning of asynchronous thread when error occurs
Typically, an exception or timeout returns a caller error message, while an exception handles some cleanup and close operations or logs the exception.
Implementation of Asynchronous request based on Servlet
Let's directly look at an example of an asynchronous request based on Servlet:
@ GetMapping (value = "/ email/send") public void servletReq (HttpServletRequest request) {AsyncContext asyncContext = request.startAsync (); / / set listeners: you can set callback handling asyncContext.addListener (new AsyncListener () {@ Override public void onTimeout (AsyncEvent event) {System.out.println ("processing timed out...") for events such as start, completion, exception, timeout, etc.) } @ Override public void onStartAsync (AsyncEvent event) {System.out.println ("Thread starts execution");} @ Override public void onError (AsyncEvent event) {System.out.println ("error occurred during execution:" + event.getThrowable () .getMessage ()) } @ Override public void onComplete (AsyncEvent event) {System.out.println ("execution completed, release resources");}}); / / set timeout asyncContext.setTimeout (6000); asyncContext.start (new Runnable () {@ Override public void run () {try {Thread.sleep (5000)) System.out.println ("Internal Threads:" + Thread.currentThread () .getName ()); asyncContext.getResponse () .getWriter () .println ("async processing");} catch (Exception e) {System.out.println ("Asynchronous handling exception:" + e.getMessage ()) } / / Asynchronous request completion notification, the entire request is completed by asyncContext.complete ();}}); / / at this time the request thread connection has released System.out.println ("main thread:" + Thread.currentThread () .getName ());}
Start the project, access the corresponding URL, and print the log as follows:
Main thread: http-nio-8080-exec-4
Internal thread: http-nio-8080-exec-5
Execution complete, release resources
As you can see, the above code first completes the log printing of the main thread, that is, the last line of the program, and then the execution of the internal thread. The internal thread execution is complete and the onComplete method of the AsyncContext is called.
If you access the corresponding URL through a browser, you can also see the return value of the method "async processing". Indicates that the result of the internal thread is also returned to the client normally.
Implementation of Asynchronous request based on Spring
Based on Spring, asynchronous requests can be implemented through Callable, DeferredResult or WebAsyncTask.
Implementation based on Callable
For a request (/ email), the Callable-based process is as follows:
1. Spring MVC starts sub-threading business (submit Callable to TaskExecutor)
2. DispatcherServlet and all Filter exit threads from the Web container, but response remains open
3. Callable returns the result, and SpringMVC sends the original request to the container (request / email again) to resume the previous processing.
4. DispatcherServlet is called again to return the result to the user
An example of code implementation is as follows:
@ GetMapping ("/ email") public Callable order () {System.out.println ("main thread start:" + Thread.currentThread (). GetName ()); Callable result = ()-> {System.out.println ("secondary thread start:" + Thread.currentThread (). GetName ()); Thread.sleep (1000); System.out.println ("secondary thread returns:" + Thread.currentThread (). GetName () Return "success";}; System.out.println ("main thread returns:" + Thread.currentThread () .getName ()); return result;}
Access the corresponding URL, and enter the log in the console as follows:
Start of the main thread: http-nio-8080-exec-1
Main thread returns: http-nio-8080-exec-1
Secondary thread start: task-1
Secondary thread returns: task-1
As you can see from the log, the main thread has completed before the secondary thread executes. Meanwhile, URL returns the result "success". This also shows that asynchronous processing on the server side is invisible to the client.
Callable is executed by default using the SimpleAsyncTaskExecutor class, which is very simple and does not reuse threads. In practice, you need to use the AsyncTaskExecutor class to configure threads.
Here, the thread pool is configured by implementing the WebMvcConfigurer interface.
@ Configurationpublic class WebConfig implements WebMvcConfigurer {@ Resource private ThreadPoolTaskExecutor myThreadPoolTaskExecutor; / * configure thread pool * / @ Bean (name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor () {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor (); taskExecutor.setCorePoolSize (2); taskExecutor.setMaxPoolSize (10); taskExecutor.setQueueCapacity (25); taskExecutor.setKeepAliveSeconds (200); taskExecutor.setThreadNamePrefix ("thread-pool-") / / Thread pool's strategy for rejecting tasks (wireless programs are available). Currently, only AbortPolicy and CallerRunsPolicy are supported. Default is the latter taskExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.CallerRunsPolicy ()); taskExecutor.initialize (); return taskExecutor;} @ Override public void configureAsyncSupport (final AsyncSupportConfigurer configurer) {/ / handle callable timeout configurer.setDefaultTimeout (60 * 1000); configurer.setTaskExecutor (myThreadPoolTaskExecutor); configurer.registerCallableInterceptors (timeoutCallableProcessingInterceptor ());} @ Bean public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor () {return new TimeoutCallableProcessingInterceptor () }}
To verify the printed thread, we replaced the System.out.println in the example code with the log output, and found that before using the thread pool, the log was printed as follows:
2021-02-21 109 c.s.learn.controller.AsynController 45 c.s.learn.controller.AsynController 37.144 INFO 8312-[main] main thread start: http-nio-8080-exec-1
2021-02-21 109 c.s.learn.controller.AsynController 45 c.s.learn.controller.AsynController 37.144 INFO 8312-[main] main thread returns: http-nio-8080-exec-1
2021-02-21 109 c.s.learn.controller.AsynController 45 INFO 37.148 8312-[task-1] start of secondary thread: task-1
2021-02-21 109 c.s.learn.controller.AsynController 45 INFO 38.153 8312-[task-1] c.s.learn.controller.AsynController: secondary thread returns: task-1
The thread name is "task-1". After allowing the thread pool to take effect, print the log as follows:
2021-02-2109 c.s.learn.controller.AsynController 50 INFO 28.950 8339-[nio-8080-exec-1] main thread start: http-nio-8080-exec-1
2021-02-21 09 c.s.learn.controller.AsynController 50 INFO 28.951 INFO 8339-[nio-8080-exec-1] main thread returns: http-nio-8080-exec-1
2021-02-2109 c.s.learn.controller.AsynController 50 INFO 28.955 8339-[thread-pool-1] start of secondary thread: thread-pool-1
2021-02-21 09 INFO 50 INFO 29.956 8339-[thread-pool-1] c.s.learn.controller.AsynController: secondary thread returns: thread-pool-1
The name of the thread is "thread-pool-1", where the preceding "thread-pool" is the thread pool prefix we configured.
In addition to the configuration of the thread pool, you can also configure uniform exception handling, which will not be demonstrated here.
Implementation based on WebAsyncTask
WebAsyncTask provided by Spring is a wrapper for Callable, providing more powerful features, such as handling timeout callbacks, error callbacks, completing callbacks, and so on.
@ GetMapping ("/ webAsyncTask") public WebAsyncTask webAsyncTask () {log.info ("external thread:" + Thread.currentThread (). GetName ()); WebAsyncTask result = new WebAsyncTask (60 * 1000L, new Callable () {@ Override public String call () {log.info ("internal thread:" + Thread.currentThread (). GetName ()); return "success";}})) Result.onTimeout (new Callable () {@ Override public String call () {log.info ("timeout callback"); return "timeout callback";}}); result.onCompletion (new Runnable () {@ Override public void run () {log.info ("finish callback");}}); return result;}
Access the corresponding request and print the log:
2021-02-21 10 c.s.learn.controller.AsynController 22 virtual 33.028 INFO 8547-[nio-8080-exec-1] external thread: http-nio-8080-exec-1
2021-02-21 10 c.s.learn.controller.AsynController 22 c.s.learn.controller.AsynController 33.033 INFO 8547-[thread-pool-1] Internal thread: thread-pool-1
2021-02-21 10 c.s.learn.controller.AsynController 22 charge 33.055 INFO 8547-[nio-8080-exec-2] c.s.learn.controller.AsynController: finish callback
Implementation based on DeferredResult
DeferredResult is used in a similar way to Callable, but unlike when returning results, the actual results may not be generated, and the actual results may be set to DeferredResult in another thread.
This feature of DeferredResult is very important for advanced applications such as service push technology, order expiration time processing, long polling, MQ simulation and so on.
Let's take a look at the official examples and instructions for the use of DeferredResult:
@ RequestMapping ("/ quotes") @ ResponseBodypublic DeferredResult quotes () {DeferredResult deferredResult = new DeferredResult (); / / Save the deferredResult in in-memory queue. Return deferredResult;} / / In some other thread...deferredResult.setResult (data)
In the above example, we can find that the call to DeferredResult is not necessarily in Spring MVC, it can be another thread. The official explanation is the same:
In this case the return value will also be produced from a separate thread. However, that thread is not known to Spring MVC. For example the result may be produced in response to some external event such as a JMS message, a scheduled task, etc.
That is, the results returned by DeferredResult may also be triggered by MQ, scheduled tasks, or other threads. Let's take an example:
@ Controller@RequestMapping ("/ async/controller") public class AsyncHelloController {private List deferredResultList = new ArrayList (); @ ResponseBody @ GetMapping ("/ hello") public DeferredResult helloGet () throws Exception {DeferredResult deferredResult = new DeferredResult (); / / save first, wait for deferredResultList.add (deferredResult); return deferredResult } @ ResponseBody @ GetMapping ("/ setHelloToAll") public void helloSet () throws Exception {/ / Let all requests residing in hold give response deferredResultList.forEach (d-> d.setResult ("say hello to all"));}}
The first request / hello will save the deferredResult first, and the front-end page will be waiting (circling). All relevant pages will not respond until the second request, setHelloToAll, is issued.
The whole execution process is as follows:
Controller returns a DeferredResult and saves it in memory or List (for later access)
Spring MVC calls request.startAsync () to turn on asynchronous processing; at the same time, the interceptor, Filter, and so on in the DispatcherServlet are immediately exited from the main thread, but the response remains open
The application gives the DeferredResult# setResult value through another thread (possibly a MQ message, a timing task, and so on). Then SpringMVC will send the request to the servlet container again
DispatcherServlet is called again and the subsequent standard process is processed.
Through the above process, it can be found that some persistent connection functions can be realized by using DeferredResult. For example, when an operation is asynchronous, the corresponding DeferredResult object can be saved first, and when the asynchronous notification comes back, the DeferredResult object can be found and the result can be processed in setResult. To improve performance.
Asynchronous implementation in SpringBoot
Declaring a method as asynchronous in SpringBoot is as simple as @ EnableAsync and @ Async with two annotations. @ EnableAsync is used to enable the asynchronous support of SpringBoot, which is used on the startup class of SpringBoot. @ Async is used on a method, marking it as an asynchronous processing method.
It is important to note that @ Async does not support methods on classes annotated by @ Configuration. In the same class, one method calls another method with @ Async, and the annotation will not take effect.
Example of the use of @ EnableAsync:
@ SpringBootApplication@EnableAsyncpublic class App {public static void main (String [] args) {SpringApplication.run (App.class, args);}}
Example of the use of @ Async:
@ Servicepublic class SyncService {@ Async public void asyncEvent () {/ / Business processing}}
The use of the @ Async annotation is similar to that of Callable. By default, the SimpleAsyncTaskExecutor thread pool is used. You can refer to the way in Callable to customize the thread pool.
Let's verify it with an example, start using @ EnableAsync on the class, and then define the Controller class:
RestControllerpublic class IndexController {@ Resource private UserService userService; @ RequestMapping ("/ async") public String async () {System.out.println ("--IndexController--1"); userService.sendSms (); System.out.println ("--IndexController--4"); return "success";}}
Define Service and asynchronous methods:
@ Servicepublic class UserService {@ Async public void sendSms () {System.out.println ("--sendSms--2"); IntStream.range (0,5) .forEach (d-> {try {Thread.sleep (1000);} catch (InterruptedException e) {e.printStackTrace ();}}) System.out.println ("- sendSms--3");}}
If you first comment out @ EnableAsync and @ Async annotations, that is, business requests under normal circumstances, print the log as follows:
-- IndexController--1
-- sendSms--2
-- sendSms--3
-- IndexController--4
When using @ EnableAsync and @ Async annotations, the log is printed as follows:
-- IndexController--1
-- IndexController--4
-- sendSms--2
-- sendSms--3
By comparing the logs, we can see that using the @ Async method will be treated as a child thread. Therefore, the entire sendSms method is executed after the main thread has finished executing.
Is this effect similar to the other forms of asynchronism we used above? So it has been mentioned at the beginning of the article that the so-called "difference between asynchronous calls and asynchronous requests" on the network is not stored, it is essentially the same thing, but in different forms of implementation. The asynchronous method mentioned here means that the method is processed asynchronously.
The difference between @ Async, WebAsyncTask, Callable and DeferredResult
The package is different:
@ Async:org.springframework.scheduling.annotation
WebAsyncTask:org.springframework.web.context.request.async
Callable:java.util.concurrent
DeferredResult:org.springframework.web.context.request.async
We should feel vaguely different through the package we are in. For example, @ Async is in the scheduling package, while WebAsyncTask and DeferredResult are for Web (Spring MVC), and Callable is for concurrent (concurrent) processing.
For Callable, it is usually used for asynchronous requests of Controller methods, but it can also be used as an alternative to Runable. It is different from the normal method in the return of the method:
/ / General method public String aMethod () {} / / compared with Callable method public Callable aMethod () {}
On the other hand, WebAsyncTask encapsulates Callable and provides some event callback handling, which is essentially not much different.
DeferredResult is used in a manner similar to Callable, with an emphasis on cross-thread communication.
@ Async is also a way to replace Runable and create threads instead of ourselves. And the scope of application is wider, not limited to the Controller layer, but can be any layer method.
Of course, you can also analyze it from the point of view of returning results, exception handling and so on.
Thank you for reading! This is the end of this article on "how to achieve asynchronous processing in SpringBoot". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, you can share it for more people to see!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.