Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Asynchronous processing support in Servlet 3.0

Why asynchronous processing is the new foundation of Web 2.0

  • Print
  • Feedback

Page 3 of 5

Ajax improves single-user responsiveness. Server-push technologies like Comet improve application responsiveness for collaborative, multi-user applications without the overhead of regular polling.

The client aspect of the server push techniques -- such as hidden iframes, XMLHttpRequest streaming, and some Dojo and jQuery libraries that facilitate asynchronous communication -- are outside this article's scope. Instead, our interest is on the server side, specifically how the Servlet 3.0 specification helps implement interactive applications with server push.

Efficient thread use: A universal solution

Comet (streaming and long polling) always occupies a channel waiting on the server to push back state changes. The same channel can't be reused by the client to send ordinary requests. So Comet applications typically work with two connections per client. If the application stays in thread-per-request mode, an outstanding thread will be associated with each Comet connection, resulting in more threads than the number of users. Comet applications that use the thread-per-request approach can scale only at the prohibitive expense of huge thread consumption.

Lack of asynchronous support in the Servlet 2.5 specification has caused server vendors to devise workarounds by weaving proprietary classes and interfaces that promote scalable Comet programming into the Servlet 2.5 API. For example, Tomcat has a CometProcessor class, Jetty 6 has Continuations, and Grizzly has a CometEngine class. With Servlet 3.0, you no longer need to sacrifice portability when programming scalable Comet applications. The asynchronous feature shown in Listing 1 consolidates great design ideas from multiple vendors and presents a universal, portable solution for efficient thread usage in Comet applications.

Let's look at a real-world example -- an online auction Web site. When buyers bid for an item online, it annoys them that they must keep refreshing the page to see the latest/highest bid. This is a hassle especially when the bidding is about to end. With server-push technologies, no page refresh or high-frequency Ajax polling is needed. The server simply pushes the item's bid price to the registered clients whenever a new bid is placed, as shown in Listing 2.

Listing 2. Auction watching

@WebServlet(name="myServlet", urlPatterns={"/auctionservice"}, asyncSupported=true)
public class MyServlet extends HttpServlet {
   
   // track bid prices
   public void doGet(HttpServletRequest request, HttpServletResponse response) {
      AsyncContext aCtx = request.startAsync(request, response); 
      // This could be a cluser-wide cache.
      ServletContext appScope = request.getServletContext();    
      Map<String, List<AsyncContext>> aucWatchers = (Map<String, List<AsyncContext>>)appScope.getAttribute("aucWatchers");
      List<AsyncContext> watchers = (List<AsyncContext>)aucWatchers.get(request.getParameter("auctionId"));
      watchers.add(aCtx); // register a watcher
   }

   // place a bid
   public void doPost(HttpServletRequest request, HttpServletResponse response) {
      // run in a transactional context 
      // save a new bid
      AsyncContext aCtx = request.startAsync(request, response); 
      ServletContext appScope = request.getServletContext(); 
      Queue<Bid> aucBids = (Queue<Bid>)appScope.getAttribute("aucBids");
      aucBids.add((Bid)request.getAttribute("bid"));  // a new bid event is placed queued.  
   }
}

@WebServletContextListener
public class BidPushService implements ServletContextListener{

   public void contextInitialized(ServletContextEvent sce) {   
      Map<String, List<AsyncContext>> aucWatchers = new HashMap<String, List<AsyncContext>>();
      sce.getServletContext().setAttribute("aucWatchers", aucWatchers);
      // store new bids not published yet
      Queue<Bid> aucBids = new ConcurrentLinkedQueue<Bid>();
      sce.getServletContext().setAttribute("aucBids", aucBids);
        
      Executor bidExecutor = Executors.newCachedThreadPool(); 
      final Executor watcherExecutor = Executors.newCachedThreadPool();
      while(true)
      {        
         if(!aucBids.isEmpty()) // There are unpublished new bid events.
         {
            final Bid bid = aucBids.poll();
            bidExecutor.execute(new Runnable(){
               public void run() {
                  List<AsyncContext> watchers = aucWatchers.get(bid.getAuctionId()); 
                  for(final AsyncContext aCtx : watchers)
                  {
                     watcherExecutor.execute(new Runnable(){
                        public void run() {
                           // publish a new bid event to a watcher
                           aCtx.getResponse().getWriter().print("A new bid on the item was placed. The current price ..., next bid price is ...");
                        };
                     });
                  }                           
               }
            });
         }
      }
   }
    
   public void contextDestroyed(ServletContextEvent sce) {
   }
}

Listing 2 is quite self-explanatory. For all the auction watchers, the uncommitted, long-lived response objects are stored in the AsynchContext objects, which are cached in an application-scoped Map. The original threads initiating the request/response loop are then returned to the thread pool and ready to serve other tasks. Whenever a new bid is placed, threads initiated in the ServletContextListener object pick it up from a queue, and then push the event to all the watchers (stored response objects) registered for the same auction. The new bid data is streamed to the connected browsers without being explicitly requested by the clients. These response objects are either uncommitted for streaming, or committed for long polling. As you can see, no threads are consumed for watchers when no bid event is happening on the sever side.

  • Print
  • Feedback

Resources

More from JavaWorld