Async servlet was introduced in Servlet 3. It’s a great way to deal with thread starvation problem with long running threads.
Async Servlet
Before we jump into understanding what Async Servlet is, let’s try to understand why do we need it.
Let’s say we have a Servlet that takes a lot of time to process, something like below.
package com.journaldev.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LongRunningServlet")
public class LongRunningServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
System.out.println("LongRunningServlet Start::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId());
String time = request.getParameter("time");
int secs = Integer.valueOf(time);
// max 10 seconds
if (secs > 10000)
secs = 10000;
longProcessing(secs);
PrintWriter out = response.getWriter();
long endTime = System.currentTimeMillis();
out.write("Processing done for " + secs + " milliseconds!!");
System.out.println("LongRunningServlet Start::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
private void longProcessing(int secs) {
// wait for given time before finishing
try {
Thread.sleep(secs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
If we hit above servlet through browser with URL as https://localhost:8080/AsyncServletExample/LongRunningServlet?time=8000
, we get response as “Processing done for 8000 milliseconds!!” after 8 seconds. Now if you will look into server logs, you will get following log:
LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103
LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103::Time Taken=8002 ms.
So our Servlet Thread was running for ~8+ seconds, although most of the processing has nothing to do with the servlet request or response.
This can lead to Thread Starvation – since our servlet thread is blocked until all the processing is done. If server gets a lot of requests to process, it will hit the maximum servlet thread limit and further requests will get Connection Refused errors.
Prior to Servlet 3.0, there were container specific solution for these long running threads where we can spawn a separate worker thread to do the heavy task and then return the response to client. The servlet thread returns to the servlet pool after starting the worker thread. Tomcat’s Comet, WebLogic’s FutureResponseServlet and WebSphere’s Asynchronous Request Dispatcher are some of the example of implementation of asynchronous processing.
The problem with container specific solution is that we can’t move to other servlet container without changing our application code, that’s why Async Servlet support was added in Servlet 3.0 to provide standard way for asynchronous processing in servlets.
Asynchronous Servlet Implementation
Let’s see steps to implement async servlet and then we will provide async supported servlet for above example.
- First of all the servlet where we want to provide async support should have @WebServlet annotation with asyncSupported value as true.
- Since the actual work is to be delegated to another thread, we should have a thread pool implementation. We can create thread pool using Executors framework and use servlet context listener to initiate the thread pool.
- We need to get instance of AsyncContext through
ServletRequest.startAsync()
method. AsyncContext provides methods to get the ServletRequest and ServletResponse object references. It also provides method to forward the request to another resource using dispatch() method. - We should have a Runnable implementation where we will do the heavy processing and then use AsyncContext object to either dispatch the request to another resource or write response using ServletResponse object. Once the processing is finished, we should call AsyncContext.complete() method to let container know that async processing is finished.
- We can add AsyncListener implementation to the AsyncContext object to implement callback methods – we can use this to provide error response to client incase of error or timeout while async thread processing. We can also do some cleanup activity here.
Once we will complete our project for Async servlet Example, it will look like below image.
Initializing Worker Thread Pool in Servlet Context Listener
package com.journaldev.servlet.async;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class AppContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
// create the thread pool
ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
servletContextEvent.getServletContext().setAttribute("executor",
executor);
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
.getServletContext().getAttribute("executor");
executor.shutdown();
}
}
The implementation is pretty straight forward, if you are not familiar with Executors framework, please read Thread Pool Executor.
For more details about listeners, please go through Servlet Listener Example.
Worker Thread Implementation
package com.journaldev.servlet.async;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
public class AsyncRequestProcessor implements Runnable {
private AsyncContext asyncContext;
private int secs;
public AsyncRequestProcessor() {
}
public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
this.asyncContext = asyncCtx;
this.secs = secs;
}
@Override
public void run() {
System.out.println("Async Supported? "
+ asyncContext.getRequest().isAsyncSupported());
longProcessing(secs);
try {
PrintWriter out = asyncContext.getResponse().getWriter();
out.write("Processing done for " + secs + " milliseconds!!");
} catch (IOException e) {
e.printStackTrace();
}
//complete the processing
asyncContext.complete();
}
private void longProcessing(int secs) {
// wait for given time before finishing
try {
Thread.sleep(secs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Notice the use of AsyncContext
and it’s usage in getting request and response objects and then completing the async processing with complete() method call.
AsyncListener Implementation
package com.journaldev.servlet.async;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
@WebListener
public class AppAsyncListener implements AsyncListener {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onComplete");
// we can do resource cleanup activity here
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onError");
//we can return error response to client
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onStartAsync");
//we can log the event here
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onTimeout");
//we can send appropriate response to client
ServletResponse response = asyncEvent.getAsyncContext().getResponse();
PrintWriter out = response.getWriter();
out.write("TimeOut Error in Processing");
}
}
Notice the implementation of onTimeout()
method where we are sending timeout response to client.
Async Servlet Example implementation
Here is the implementation of our async servlet, notice the use of AsyncContext and ThreadPoolExecutor for processing.
package com.journaldev.servlet.async;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet Start::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId());
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
String time = request.getParameter("time");
int secs = Integer.valueOf(time);
// max 10 seconds
if (secs > 10000)
secs = 10000;
AsyncContext asyncCtx = request.startAsync();
asyncCtx.addListener(new AppAsyncListener());
asyncCtx.setTimeout(9000);
ThreadPoolExecutor executor = (ThreadPoolExecutor) request
.getServletContext().getAttribute("executor");
executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
long endTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet End::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
}
Run Async Servlet web application
Now when we will run above servlet with URL as https://localhost:8080/AsyncServletExample/AsyncLongRunningServlet?time=8000
we get the same response and logs as:
AsyncLongRunningServlet Start::Name=http-bio-8080-exec-50::ID=124
AsyncLongRunningServlet End::Name=http-bio-8080-exec-50::ID=124::Time Taken=1 ms.
Async Supported? true
AppAsyncListener onComplete
If we run with time as 9999, timeout occurs and we get response at client side as “TimeOut Error in Processing” and in logs:
AsyncLongRunningServlet Start::Name=http-bio-8080-exec-44::ID=117
AsyncLongRunningServlet End::Name=http-bio-8080-exec-44::ID=117::Time Taken=1 ms.
Async Supported? true
AppAsyncListener onTimeout
AppAsyncListener onError
AppAsyncListener onComplete
Exception in thread "pool-5-thread-6" java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.
at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:439)
at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:197)
at com.journaldev.servlet.async.AsyncRequestProcessor.run(AsyncRequestProcessor.java:27)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:680)
Notice that servlet thread finished execution quickly and all the major processing work is happening in other thread.
Thats all for Async Servlet, I hope you liked it.
Hello Sir
My name is Ashish. I am pursuing my graduation degree. I need your help, Sir i was developing My last year project. It is web application using jsp and servlet. Project is like exam system, teacher will add question on jsp page and it will process on jsp page. whille adding question to database using servlet, on servlet page it takes long time to process single request. means the logic written is long process and it needed, to add one question servlet take minimum 80-90 seconds and its very time consuming . So i want to implement asynchronous servlet. I was using apache tomcat in project. So can you guide me how to implement asyn servlet. so user/teacher don’t have to wait servlet end the process. means servlet can work in background and user don’t need to take tension of process. he can easily add question one by one. So i will wait for your response.
Thank You.
A thread is free while another thread is blocked.How is this improve the performance?
It improves the number of potentially servicable requests by the number of threads that you can place into async mode. The assumption is that the majority of requests will not be long-running requests and can be fulfilled in the service thread. It is only a select few that will need to be placed into async mode. With that said, servicing/completing the request immediately frees up that thread to service another request. Your container will have to create fewer threads and/or is less likely to reach its maximum service thread pool size…therefore improving performance.
The problem is that when you would invoke the servlet through URL in a browser and say you have given time as 8000, you would notice that the web page is in loading status and would complete after 8000 ms / 8 seconds. Then is it truly async in this case ? since the browser waits for the long process to complete which is on another spawned thread and then sends the response back once this second thread gets completed.
Thank you for sharing information on your website, blogging greetings
hay. very nice post.
i want to “prove” the problem with LongRunningServlet .
what i mean is to invoke Thousands of ” LongRunningServlet .” , after i declare in the tomcat (for example) a small amount of threads by declaring max thread=5.
i expect that after a while i will get connection refuse or some other error.
but everything is ok and all the request return right response.
what i’m missing here?
10x
Nice!! one confusion, why are you using @WebListener annotation on AppAsyncListener and creating the instance of the listener with “new” in the servlet. @WebListener is only supposed to be used with the listeners managed by the container right?
@WebListener on AsyncListener doesn’t work. You have to add it on the context.
Thanks, nice post
How we can handle upload file (multipart) by using AsyncContext (in AsyncRequestProcessor)?