In a typical servlet scenario, only one copy of any particular servlet is loaded at any given time. Each servlet might, however, be called upon to deal with multiple requests at the same time. This means that a servlet needs to be thread-safe. If a servlet doesn't use any class variables (that is, any variables with a scope broader than the service method itself), it is generally already thread-safe. If you are using any third-party libraries or extensions, make sure that those components are also thread-safe. However, a servlet that maintains persistent resources needs to make sure that nothing untoward happens to those resources. Imagine, for example, a servlet that maintains a bank balance using an int in memory.[6] If two servlets try to access the balance at the same time, we might get this sequence of events:
[6] Hey, bear with me on this one. It's certainly more than adequate for my salary...
User 1 connects to the servlet to make a $100 withdrawal. The servlet checks the balance for User 1, finding $120. User 2 connects to the servlet to make a $50 withdrawal. The servlet checks the balance for User 2, finding $120. The servlet debits $100 for User 1, leaving $20. The servlet debits $50 for User 2, leaving -$30. The programmer is fired.
Obviously, this is incorrect behavior, particularly that last bit. We want the servlet to perform the necessary action for User 1, and then deal with User 2 (in this case, by giving him an insufficient funds message). We can do this by surrounding sections of code with synchronized blocks. While a particular synchronized block is executing, no other sections of code that are synchronized on the same object (usually the servlet or the resource being protected) can execute. For more information on thread safety and synchronization, see Java Threads by Scott Oaks and Henry Wong (O'Reilly).
Example 5-5 implements the ATM display for the First Bank of Java. The doGet() method displays the current account balance and provides a small ATM control panel for making deposits and withdrawals, as shown in Figure 5-3.[7] The control panel uses a POST request to send the transaction back to the servlet, which performs the appropriate action and calls doGet() to redisplay the ATM screen with the updated balance.
[7] Despite the fact that Java is a very large island, there's still only one account.
import javax.servlet.*; import javax.servlet.http.*; import java.util.*; import java.io.*; public class AtmServlet extends HttpServlet { Account act; public void init(ServletConfig conf) throws ServletException { super.init(conf); act = new Account(); act.balance = 0; } public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<HTML><BODY>"); out.println("<H2>First Bank of Java ATM</H2>"); out.println("Current Balance: <B>" + act.balance + "</B><BR>"); out.println("<FORM METHOD=POST ACTION=/servlet/AtmServlet>"); out.println("Amount: <INPUT TYPE=TEXT NAME=AMOUNT SIZE=3><BR>"); out.println("<INPUT TYPE=SUBMIT NAME=DEPOSIT VALUE=\"Deposit\">"); out.println("<INPUT TYPE=SUBMIT NAME=WITHDRAW VALUE=\"Withdraw\">"); out.println("</FORM>"); out.println("</BODY></HTML>"); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int amt=0; try { amt = Integer.parseInt(req.getParameter("AMOUNT")); } catch (NullPointerException e) { // No Amount Parameter passed } catch (NumberFormatException e) { // Amount Parameter was not a number } synchronized(act) { if(req.getParameter("WITHDRAW") != null) && (amt < act.balance) act.balance = act.balance - amt; if(req.getParameter("DEPOSIT") != null) && (amt > 0) act.balance = act.balance + amt; } // end synchronized block doGet(req, resp); // Show ATM screen } public void destroy() { // This is where we would save the balance to a file } class Account { public int balance; } }
The doPost() method alters the account balance contained within an Account object act (since Account is so simple, I've defined it as an inner class). In order to prevent multiple requests from accessing the same account at once, any code that alters act is synchronized on act. This ensures that no other code can alter act while a synchronized section is running.
The destroy() method is defined in the AtmServlet, but it contains no actual code. A real banking servlet would obviously want to write the account balance to disk before being unloaded. And if the servlet were using JDBC to store the balance in a database, it would also want to destroy all its database related objects.
A more complex servlet than AtmServlet might need to synchronize its entire service method, limiting the servlet to one request at a time. In these situations, it sometimes makes sense to modify the standard servlet life cycle a little bit. We can do this by implementing the SingleThreadModel interface. This is a tag interface that has no methods; it simply tells the server to create a pool of servlet instances, instead of a single instance of the servlet. To handle an incoming request, the server uses a servlet from the pool and only allows each copy of the servlet to serve one request at a time. Implementing this interface effectively makes a servlet thread-safe, while allowing the server to deal with more than one connection at a time. Of course, using SingleThreadModel does increase resource requirements and make it difficult to share data objects within a servlet.
Another use for SingleThreadModel is to implement simple database connection sharing. Having multiple database connections can improve performance and avoid connection overloading. Of course, for more advanced or high-traffic applications, you generally want to manage connection pooling explicitly, rather than trusting the web server to do it for you.
Copyright © 2001 O'Reilly & Associates. All rights reserved.