This is the 22nd day of my participation in the August More Text Challenge

Java Web Servlet3.0 provides the principle of asynchronous request processing mechanism, and provides a use case!

1 Overview of asynchronous processing

By default, a Web container (such as Tomcat) allocates one request-processing thread per request (in Tomcat 7/8, the default number of threads that can process incoming requests at the same time is 200), and by default, the thread resource is not released until the response is complete. In other words, the HTTP request is processed by the same thread that executes the specific business code!

If the business code in a Servlet or Filter takes a long time to process, such as database operations and other cross-network calls, then one request processing thread will be occupied until the task is finished. In this case, as the number of concurrent requests increases, it is possible that all request processing threads will be occupied. At this point, Tomcat will stack subsequent requests into the internal blocking queue container. If the blocking queue is full, subsequent incoming requests will be denied service until there are thread resources available to handle the requests.

Servlet 3.0 began to support asynchronous processing of requests. After receiving a request, the Servlet thread can delegate the time-consuming operation to another thread, returning itself to the container without generating a response so that it can process another request. The response to the current request is delayed until the asynchronous processing is complete (the asynchronous thread has references to ServletRequest and ServletResponse objects).

When asynchronous request processing is enabled, instead of being blocked waiting for business logic to process, the Servlet thread can return immediately after the asynchronous thread is started. Asynchronous processing features can help applications save threads in the container, especially for tasks that take a long time to execute and require users to get response results, which will greatly reduce the footprint of server resources and improve concurrent processing speed. If the user does not need the result, simply hand a Runnable object to the Executor in memory and return the response immediately.

We can also see that,In fact, the asynchronous request processing here is still a synchronous output to the client browser, which does not improve the response speed and is not perceived by the userHowever, asynchronous request processing frees up the use of server-side request processing threads. Instead of being stuck waiting in the business code, the current business logic is transferred to other threads, enabling Tomcat to accept more requests at the same time, thus increasing the ability to process requests concurrently!

In addition, Servlet asynchronous processing and Tomcat NIO are two different concepts, and We have discussed tomcat NIO mode previously:Java Web(2) – Tomcat server. XML configuration file details.

Use of asynchronous request processing

2.1 Enabling Asynchronous Support

Asynchronous processing needs to be enabled before servlets and filters can use asynchronous processing!

This can be enabled in web.xml:

<servlet>
    <servlet-name>AsyncServlet</servlet-name>
    <servlet-class>com.example.async.AsyncServlet</servlet-class>
    <! -- Servlet enable asynchronous processing -->
    <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
    <servlet-name>AsyncServlet</servlet-name>
    <url-pattern>/AsyncServlet</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>AsyncFilter</filter-name>
    <filter-class>com.example.async.AsyncFilter</filter-class>
    <! --filter enable asynchronous processing -->
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>AsyncFilter</filter-name>
    <servlet-name>AsyncServlet</servlet-name>
</filter-mapping>
Copy the code

It is more convenient to open it directly with a comment:

@WebServlet(urlPatterns = "/AsyncServlet",asyncSupported = true)

@WebFilter(servletNames = "AsyncServlet", asyncSupported = true)
Copy the code

2.2 Writing asynchronous Servlets and Filters

Writing an asynchronous Serlvet or Filter is relatively simple. If you have a task that takes a relatively long time to complete, it is best to create an asynchronous Servlet or Filter, where the asynchronous Servlet or Filter class does the following:

  1. Call the request.startAsync() method to get an AsyncContext object.
    1. The former method creates an AsyncContext directly from the existing request and response objects. Request and response objects can be obtained via AsyncContext’s getRequest() and getResponse() methods. You can also use the startAsync method with two parameters to pass in a self-created request, response wrapper object.
    2. This time the response to the client is deferred until the AsyncContext complete() or Dispatch () methods are called, or the asynchronous request times out.
  2. Call the asyncContext.setTimeout() method to set the number of milliseconds a container must wait for a specified task to complete. This step is optional, but if this time limit is not set, the container’s default time will be used. If the task does not complete within the specified full limit, an exception may be thrown, and if the task is completed but not committed, an attempt will be made to complete() when the timeout expires. The default time is 30000 ms. If the value is zero or negative, no timeout is set.
  3. Call the asyncContext.addListener() method to set an asynchronous request listener (optional). This listener must be an implementation of the AsyncListener interface. The AsyncListener interface has the following methods that listen for different events of an asynchronous request:
    1. OnComplete: Asynchronous requests are called back after the complete() or dispatch() methods are called, or automatically after a timeout.
    2. OnError: callback when an asynchronous request is abnormal. Some exceptions may not be called back, but rather the onTimeout and onComplete methods will be called back after waiting for a timeout.
    3. OnStartAsync: startAsync() callback failed.
    4. OnTimeout: callback after an asynchronous request times out. OnTimeout is called, followed by onComplete.
  4. Call the asyncContext.start() method and pass a Runnable of the time task. The Runnable is the business code logic we need to execute.
    1. It is important to note that this method performs the task through a new thread, but actually still calls a thread from the Connector thread pool, which by default is still a thread pool from Servlet threads. In this case, we wanted to release the Servlet thread, but we didn’t, because there was still another Servlet thread to perform the task.
    2. For this reason, it is better to maintain a separate global thread pool to perform business tasks in your Web application so that asynchronous request processing is truly possible.
  5. Within Runnable, the asyncContext.plete () method or asyncContext.dispatch(String path) method is called when the task is completed. The former indicates that the response is complete, while the latter indicates that the specified URL will be dispatched to respond.
    1. Dispatch (String path) method. The parameters of path and ServletRequest getRequestDispatcher (String) method is the path of the request is consistent, and similar to the request contains!
    2. Executing the complete() or Dispatch (String Path) method multiple times will throw an exception!
    3. If the complete() or dispatch(String path) methods are not executed, the response will wait until a timeout occurs!

A simple asynchronous Servlet example is as follows:

@WebServlet(name = "AsyncServlet", urlPatterns = "/AsyncServlet", asyncSupported = true)
public class AsyncServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        / / the Servlet thread
        System.out.println("Servlet thread:" + Thread.currentThread().getName());

        /* * get asyncContext */
        AsyncContext asyncContext = request.startAsync();

        /* * Set the timeout in milliseconds */
        //asyncContext.setTimeout(4000);

        /* * Sets asynchronous listeners. Listener methods may be executed by other threads
        asyncContext.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent event) {
                System.out.println(Callback to complete asynchronous request or timeout: + Thread.currentThread().getName());
            }

            @Override
            public void onTimeout(AsyncEvent event) {
                System.out.println("Timeout callback:" + Thread.currentThread().getName());
            }

            @Override
            public void onError(AsyncEvent event) {
                System.out.println("Exception callback:" + Thread.currentThread().getName());
            }

            @Override
            public void onStartAsync(AsyncEvent event) {
                System.out.println("Callback when startAsync is executed:"+ Thread.currentThread().getName()); }});/* * This method calls a thread in the Connector thread pool to execute the thread task. * /
        asyncContext.start(() -> {
            // The thread that executes the task
            System.out.println("Thread executing task:" + Thread.currentThread().getName());
            // Asynchronously executed task code
            //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
            // If the execution time exceeds the timeout, an exception may be thrown
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));

            // Throws an exception that may not raise an onError event
            //int i = 1 / 0;

            PrintWriter out;
            try {
                response.setContentType("text/html");
                out = response.getWriter();
                out.println("hello_hello");
            } catch (IOException e) {
                e.printStackTrace();
            }

            // called when the task is complete
            // Indicates that the response is complete
            asyncContext.complete();

            // indicates that the specified URL will be dispatched to respond
            //asyncContext.dispatch("/index.jsp");

            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
            System.out.println(The execution has been complete.);
        });
        /* * You can also create your own thread or thread pool for execution, it is better to use a self-maintained global thread pool in your application */
// new Thread(() -> {
// // The thread that executes the task
// system.out.println (" Thread running: "+ thread.currentThread ().getName());
// // Asynchronously executed task code
// //LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
// // If the execution time exceeds the timeout period, an exception may be thrown
// LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
//
// // throws an exception that may not raise an onError event
// //int i = 1 / 0;
//
// PrintWriter out;
// try {
// out = response.getWriter();
// out.println("hello_hello");
// } catch (IOException e) {
// e.printStackTrace();
/ /}
//
// // called when the task is complete
// // indicates that the response is complete
// asyncContext.complete();
//
// // indicates that the specified URL will be dispatched to respond
// //asyncContext.dispatch("/index.jsp");
//
// LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
// system.out. println("complete ");
// }).start();}}Copy the code

If you need to communicate, or the article is wrong, please leave a message directly. In addition, I hope to like, collect, pay attention to, I will continue to update a variety of Java learning blog!