Recently updated with a series of articles on asynchrony and callbacks, such as “An article to Understand the Difference between asynchrony and multithreading”, “Two classic Examples to Help You Understand the Java callback mechanism once and for all”, “What is the difference between asynchronous requests and asynchronous calls? If you’re interested, you can review it.

Today we will take you to learn a summary of the four forms of asynchronous processing in SpringBoot, the following text:

preface

There are two kinds of statements about asynchronous request and asynchronous call of SpringBoot on the network. After calling these two statements are essentially the same thing. In “What is the difference between asynchronous request and asynchronous call? One, which has already been explained.

At the same time, we also learned that “the asynchronous and synchronous nature of the service implementation is completely independent of the asynchronous and synchronous nature of the client invocation. This means that clients can asynchronously invoke synchronous services, and clients can asynchronously invoke asynchronous services.”

This article focuses on the use of asynchrony in SpringBoot (including asynchronous invocation and asynchronous methods).

Asynchronous versus synchronous requests

Let’s start with a graph to see the difference between asynchronous and synchronous requests:

In the figure above, there are three roles: client, Web container, and business process thread.

Client requests to the Web container in both processes are synchronous. Because they are all in a blocking wait state when requesting the client, they are not processed asynchronously.

In the Web container section, the first process takes the form of a synchronous request and the second takes the form of an asynchronous callback.

Through asynchronous processing, threads and related resources allocated to requests by the container can be released first, reducing the burden on the system, thereby increasing the throughput of the server to the client requests. However, when the number of concurrent requests is large, the solution is usually load balancing rather than asynchronous.

Asynchrony in Servlet3.0

Prior to Servlet 3.0, servlets handled requests in a thread-per-request manner, meaning that each Http Request was processed from beginning to end by a single Thread. Performance issues are obvious when it comes to time-consuming operations.

Servlet 3.0 provides asynchronous processing of requests. You can increase the throughput of the service by releasing the threads and related resources that the container allocates to requests, reducing the burden on the system.

Servlet 3.0 asynchrony is accomplished through the AsyncContext object, which can pass from the current thread to another thread and return the original thread. The new thread can directly return the result 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();
}
Copy the code

AsyncContext provides functions such as retrieving a ServletRequest, ServletResponse, and addListener:

public interface AsyncContext { ServletRequest getRequest(); ServletResponse getResponse(); void addListener(AsyncListener var1); void setTimeout(long var1); // omit other methods}Copy the code

You can not only obtain Request and Response information through AsyncContext, but also set the timeout time for asynchronous processing. In general, the timeout in milliseconds needs to be set, otherwise waiting indefinitely would be the same as synchronous processing.

AddListener of AsyncContext also allows you to addListener events that handle callbacks to asynchronous threads such as start, finish, exception, timeout, etc.

AddListener AsyncListener AsyncListener

Public interface AsyncListener extends EventListener {// Call void onComplete(AsyncEvent VAR1) throws IOException;  Void onTimeout(AsyncEvent VAR1) throws IOException; Void onError(AsyncEvent VAR1) throws IOException; void onError(AsyncEvent var1) throws IOException; Void onStartAsync(AsyncEvent VAR1) throws IOException; }Copy the code

Typically, an exception or timeout returns an error message to the caller, while an exception handles some cleanup and shutdown operations or logs the exception.

Asynchronous request based on Servlet

Let’s look directly at an example of an asynchronous servlet-based request:

@GetMapping(value = "/email/send") public void servletReq(HttpServletRequest request) { AsyncContext asyncContext = request.startAsync(); Asynccontext.addlistener (new AsyncListener() {@override public void addListener() OnTimeout (AsyncEvent event) {system.out.println (" Processing timed out... ); } @override public void onStartAsync(AsyncEvent event) {system.out.println (" thread starts executing "); } @override public void onError(AsyncEvent event) {system.out.println (); " + event.getThrowable().getMessage()); } @override public void onComplete(AsyncEvent event) {system.out.println (" execute, free resource "); }}); // setTimeout asynccontext. setTimeout(6000); asyncContext.start(new Runnable() { @Override public void run() { try { Thread.sleep(5000); System.out.println(" internal Thread: "+ thread.currentThread ().getName()); asyncContext.getResponse().getWriter().println("async processing"); } catch (Exception e) {system.out.println (" error: "+ LLDB message ()); } // asyncContext.complete(); }}); System.out.println(" Thread: "+ thread.currentThread ().getName())); }Copy the code

Start the project, access the corresponding URL, and print the following log:

Main thread: HTTP-nio-8080-exec-4 Internal thread: http-nio-8080-exec-5 When the execution is complete, resources are releasedCopy the code

As you can see, the above code completes the main thread first, which is the log printing of the last line of the program, and then the execution of the internal thread. The inner thread completes execution and AsyncContext’s onComplete method is called.

If you access the URL through a browser, you can also see the return value of async Processing. The result from the internal thread is also returned to the client normally.

Implement asynchronous requests based on Spring

Spring-based asynchronous requests can be implemented through Callable, DeferredResult, or WebAsyncTask.

Based on Callable

For a request (/email), the Callable processing flow is as follows:

1. Spring MVC enables sub-threads to handle business (submitting Callable to TaskExecutor);

2. DispatcherServlet and all filters exit the Web container thread, but Response remains open;

Callable returns the result, SpringMVC sends the original request back to the container (request again /email), resuming the previous processing;

4. DispatcherServlet is called again and the result is returned to the user;

Examples of code implementation are as follows:

@getMapping ("/email") public Callable<String> order() {system.out.println (" mainline start: " + Thread.currentThread().getName()); Callable<String> result = () -> {system.out.println (" thread.currentThread ().getName() "); Thread.sleep(1000); System.out.println(" + thread.currentThread ().getName()); return "success"; }; System.out.println(" thread.currentThread ().getName()); return result; }Copy the code

To access the corresponding URL, the console enters the log as follows:

Main thread start: HTTP-nio-8080-exec-1 Main thread Return: HTTP-nio-8080-exec-1 secondary thread Start: task-1 Secondary thread return: task-1Copy the code

As you can see from the log, the main thread completes before the secondary thread executes. At the same time, the URL returns the result “Success”. This also illustrates the problem 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, the AsyncTaskExecutor class is used to configure threads.

The thread pool is configured by implementing the WebMvcConfigurer interface.

@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private ThreadPoolTaskExecutor myThreadPoolTaskExecutor; / * * * * / configuration 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-"); AbortPolicy and CallerRunsPolicy are supported in the thread pool. . The default is the latter taskExecutor setRejectedExecutionHandler (new ThreadPoolExecutor CallerRunsPolicy ()); taskExecutor.initialize(); return taskExecutor; } @override public void configureAsyncSupport(Final AsyncSupportConfigurer configurer) {// Process callable timeout configurer.setDefaultTimeout(60 * 1000); configurer.setTaskExecutor(myThreadPoolTaskExecutor); configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor()); } @Bean public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() { return new TimeoutCallableProcessingInterceptor(); }}Copy the code

To verify the printed thread, we replace the system.out.println in the example code with log output, and see that the log is printed as follows before using the thread pool:

The 2021-02-21 09:45:37. 8312-144 the INFO [nio - 8080 - exec - 1] C.S.L earn. Controller. AsynController: Main thread start: HTTP - nio - 8080-2021-02-21 09:45:37 exec - 1, 8312-144 the INFO [nio - 8080 - exec - 1] C.S.L earn. Controller. AsynController: Main thread returns: HTTP - nio - 8080-2021-02-21 09:45:37 exec - 1, 8312-148 the INFO] [task - 1 C.S.L earn. Controller. AsynController: Vice thread begins: the 2021-02-21 09:45:38 task - 1. 8312-153 the INFO] [task - 1 C.S.L earn. Controller. AsynController: vice thread returns: task 1Copy the code

The thread name is task-1. After the thread pool is enabled, the following log is printed:

The 2021-02-21 09:50:28. 8339-950 the INFO [nio - 8080 - exec - 1] C.S.L earn. Controller. AsynController: Main thread start: HTTP - nio - 8080-2021-02-21 09:50:28 exec - 1, 8339-951 the INFO [nio - 8080 - exec - 1] C.S.L earn. Controller. AsynController: The main thread to return: HTTP - nio - 8080-2021-02-21 09:50:28 exec - 1. 8339-955 the INFO [thread pool - 1] C.S.L earn. Controller. AsynController: Vice thread begins: thread pool - 1 09:50:29 2021-02-21. 8339-956 the INFO [thread pool - 1] C.S.L earn. Controller. AsynController: The secondary thread returns thread-pool-1Copy the code

The thread name is thread-pool-1, where the thread pool prefix is specified.

In addition to the thread pool configuration, you can also configure unified exception handling, which is not demonstrated here.

Implementation based on WebAsyncTask

Spring provides WebAsyncTask as a wrapper around Callable, providing more powerful functions such as handling timeout callbacks, error callbacks, completion callbacks, and so on.

@getMapping ("/webAsyncTask") public webAsyncTask <String> webAsyncTask() {log.info(" thread: " + Thread.currentThread().getName()); WebAsyncTask<String> result = new WebAsyncTask<>(60 * 1000L, New Callable<String>() {@override public String call() {log.info(" internal Thread: "+ thread.currentThread ().getName()); return "success"; }}); result.onTimeout(new Callable<String>() { @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; }Copy the code

Access the corresponding request and print the log:

The 2021-02-21 10:22:33. 8547-028 the INFO [nio - 8080 - exec - 1] C.S.L earn. Controller. AsynController: External thread: HTTP - nio - 8080-2021-02-21 10:22:33 exec - 1, 8547-033 the INFO [thread pool - 1] C.S.L earn. Controller. AsynController: Internal threads, thread pool - 1 10:22:33 2021-02-21. 8547-055 the INFO [nio - 8080 - exec - 2] C.S.L earn. Controller. AsynController: finish callbackCopy the code

Implementation based on DeferredResult

DeferredResult is used in a similar way to Callable, but not in the same way as A DeferredResult. The actual result of DeferredResult may not have been generated when it is returned. The actual result of DeferredResult may have been set in another thread.

This feature of DeferredResult is important for advanced applications such as server-side pushing, order expiration handling, long polling, and the ability to emulate MQ.

In DeferredResult, let’s take a look at the official examples and instructions:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
  DeferredResult<String> deferredResult = new DeferredResult<String>();
  // Save the deferredResult in in-memory queue ...
  return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);
Copy the code

As you can see from the example above, the call to DeferredResult doesn’t have to be in Spring MVC, it could be another thread. So does the official explanation:

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, DeferredResult can also return results triggered by MQ, scheduled tasks, or other threads. Here’s an example:

@Controller @RequestMapping("/async/controller") public class AsyncHelloController { private List<DeferredResult<String>> deferredResultList = new ArrayList<>(); @ResponseBody @GetMapping("/hello") public DeferredResult<String> helloGet() throws Exception { DeferredResult<String> deferredResult = new DeferredResult<>(); Add (deferredResult); return deferredResult; } @responseBody@getMapping ("/setHelloToAll") public void helloSet() throws Exception {// Let all held requests respond deferredResultList.forEach(d -> d.setResult("say hello to all")); }}Copy the code

The first request, / Hello, will store deferredResult, and the front end of the page will wait. All relevant pages will not respond until the second request, setHelloToAll, is sent.

The entire execution process is as follows:

  • The controller returns a DeferredResult and stores it in memory or in a List (for subsequent access);
  • Spring MVC calls Request.startAsync () to enable asynchronous processing; At the same time, the interceptor, Filter and so on in DispatcherServlet will immediately exit the main thread, but the state of Response remains open;
  • The application gives the DeferredResult#setResult value through another thread (possibly MQ messages, scheduled tasks, and so on). SpringMVC then sends the request to the servlet container again;
  • The DispatcherServlet is invoked again and then processes the subsequent standard flow;

It can be found through the above process: DeferredResult can be used to implement long connections. For example, when an operation is asynchronous, you can save the corresponding DeferredResult object, and when the asynchronous notification comes back, you can find the DeferredResult object and process the result in setResult. Thus improving performance.

Asynchronous implementation in SpringBoot

Declaring a method as an asynchronous method in SpringBoot is as simple as two annotations @enableAsync and @async. In the command, @enableAsync is used to enable SpringBoot asynchrony and the SpringBoot startup class. @async is used on a method to mark it as an asynchronous processing method.

Note that @Async is not supported on methods of classes annotated by @Configuration. If a method in the same class calls another method with @async, the annotation will not take effect.

An example of how to use @enableAsync:

@SpringBootApplication @EnableAsync public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); }}Copy the code

Examples of using @async:

@service public class SyncService {@async public void asyncEvent() {// Business process}}Copy the code

The @async annotation is similar to Callable in that by default the SimpleAsyncTaskExecutor thread pool is used. You can customize the thread pool as described in Callable.

To verify this, use @enableAsync on the startup class and define the Controller class:

@RestController public 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"; }}Copy the code

Define Service and asynchronous methods:

@Service public 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"); }}Copy the code

If @enableAsync and @async annotations are first commented out, that is, service requests under normal circumstances, the following logs are generated:

--IndexController--1
--sendSms--2
--sendSms--3
--IndexController--4
Copy the code

When @enableAsync and @async annotations are used, the following logs are printed:

--IndexController--1
--IndexController--4
--sendSms--2
--sendSms--3
Copy the code

We can see from the log comparison that @async is treated as a child thread. Therefore, the entire sendSms method is executed after the main thread has finished executing.

Does this have the same effect as the other forms of asynchrony we used above? As mentioned at the beginning of this article, the so-called “difference between asynchronous invocation and asynchronous request” on the network is not stored, but is essentially the same thing, but implemented in different forms. When I say asynchronous methods, that is, methods are processed asynchronously.

@Async, WebAsyncTask, Callable, DeferredResult

Different packages:

  • @ Async: org. Springframework. Scheduling. The annotation;
  • WebAsyncTask: org. Springframework. Web. Context. Request. Async;
  • Callable: Java. Util. Concurrent;
  • DeferredResult: org. Springframework. Web. Context. Request. Async;

We should feel a little bit different from the package we are in, for example @Async is in the Scheduling package, while WebAsyncTask and DeferredResult are for the Web (Spring MVC), Callable is used for concurrent processing.

For Callable, this is usually used for asynchronous requests with the Controller method, but it can also be used as a Runable replacement. Differs from normal methods in the way they return:

Public String aMethod(){} public String <String> aMethod(){}Copy the code

WebAsyncTask is a wrapper around Callable that provides some handling of event callbacks, not much different in nature.

DeferredResult is used in a similar way to Callable, with an emphasis on communication across threads.

@async is also a way to replace Runable instead of creating our own thread. And it applies more broadly, not just to the Controller layer, but to any layer’s methods.

Of course, you can also analyze it in terms of return results, exception handling and so on, but I won’t go into that here.

summary

After the above step by step analysis, you should have some idea of asynchronous slave processing in Servlet3.0 and Spring. After understanding these basic theories, practical examples, methods of use and matters needing attention, I think I can further remove the false and retain the true knowledge on the network.

It is better to believe books than to have no books. Take us to study together, study together, discard the false and retain the true together, and pursue truly useful knowledge.

Four asynchronous processing methods for SpringBoot, which I learned myself in writing this article


Program new horizon

\

The public account “program new vision”, a platform for simultaneous improvement of soft power and hard technology, provides massive information

\