| [ directory ] |
Web servers are designed to handle many concurrent users and many concurrent requests from those users. Typically, to handle requests Web servers create many threads, with each thread handling a request. Remember that a given named Servlet is a singletonthat is, there is only one instance of a given named Servlet in memory. This means that when a Web server receives multiple requests for a Servlet, all those requests are dispatched to that Servlet on multiple concurrent threads, which means that Servlets must be written to be thread-safe. This section examines thread safety in Servlets and how to achieve it. There is no getting away from this: Servlets and JSP must be written in a thread-safe manner.
Table 9-1 shows the various "scopes" of data that an application in a Web server will use. Some data are "naturally" thread-safe and no special steps need to be taken to protect access to that data. Some data, however, may be accessed by multiple threads at the same time, and care must be taken when using that data.
In many cases it is obvious what does and does not need protecting. Local variables are thread-safe: a thread has only one stack and local variables live on that stack; only one thread can be touching those variables at any given time. Method parameters are generally safe for the same reason; in particular, HttpServletRequest and HttpServletResponse are guaranteed to be thread-safe. A Web server will either create a new request and response object for each new request, or the server will pool request and response objects. In either case each call is guaranteed to get its own request and response object.
Other objects are also obviously not thread-safe; static data, instance data, session-scoped[9] data, and application-scoped data fall into this category. These objects are worth further discussion because poorly done code that deals with these objects can result in unexpected Web Application behavior. The following sections further explain the relevant issues and how to ensure thread safety.
[9] It may not be obvious that session state can be accessed from multiple threads concurrently. Surely one client means one session means one thread. However, a given client can send multiple concurrent requests to a server. Each request will have its own thread and each thread can touch the session. How does the client do this? An application may have multiple frames; a client may send a request, cancel the request and the client; and resend the request; a client may have multiple open windows, etc., etc.
Protecting state in Java is easy in theory but more difficult in practice. Java has built-in support for synchronization with the synchronized keyword. This can be used to synchronize entire methods or as a form of "critical section", which protects access to blocks of code. The Java 2 specification defines the official thread synchronization statement and safety mechanisms in section 17, "Threads and Locks". If you are unfamiliar with threads, do take the time to read this section. The Java specifications are available free online at http://java.sun.com.
Synchronization is something that should be used minimally in an application. When synchronizing, you are essentially taking a "lock" on a block of code, and locks cause contention. Many threads could be trying to take that lock at the same time. Contention of any sort will harm scalability; contention of threads is no different. Because of this there is one golden rule to remember: Only synchronize what you have to! For example, typically, read access to data often happens far more frequently than write access. It is therefore possible to allow many readers to access data concurrently and so reduce contention. However, only one writer should ever be allowed to access an item of data. Java does not provide native read/write synchronization, but these are not difficult to develop yourself (there are many excellent resources). It may well be worth investing time in defining read locks and write locks to limit the amount of contention you have. Whatever approach an application takes, it is always important to keep lock times at a minimum.
New Servlet developers often get into trouble with thread safety because they fail to realize that a Servlet container is multi-threaded. One instance of a Servlet will be shared with many concurrent client requests. By default this is exactly what is done, and it makes a simple Servlet a highly scalable piece of code. An easy way to think of this concept is by saying a container only loads one instance of a Servlet into memory, but it calls the service methods each time a client sends a request, regardless if the previous request is finished yet.
As this applies to your code, any variable declared inside a service method[10], such as doGet(), will be unique to a specific request. However, any variable defined outside a service method is shared by all requests. This includes all requests by all clients, not just all requests from the same client. For this reason it is bad practice to declare variables outside the service methods unless there is good reason to do so. This is usually intuitive, but understand that thread safety is part of the reason why. For clarity let's look at a Servlet that is not thread-safe and point out exactly why. For example, look at Listing 9-8, which is the code for the NotThreadSafe Servlet. The goal of this Servlet is to read a few request parameters and display them to a user; however, this would not always happen correctly because the Servlet suffers from poor thread safety.
[10] The same issue is present in a JSP. Remember, declaration elements declare code outside of the JSP service method.
package com.jspbook;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class NotThreadSafe extends HttpServlet {
String value1;
String value2;
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>NotThreadSafe!</title>");
out.println("</head>");
out.println("<body>");
out.println("The values are:<br>");
value1 = request.getParameter("value1");
value2 = request.getParameter("value2");
out.println("Value 1: "+value1+"<br>");
out.println("Value 2: "+value2+"<br>");
out.println("Are they the same as submitted?");
out.println("</body>");
out.println("</html>");
}
}
The problem lies in declaring the variables as instance variables rather than local to the doGet() method. What the Servlet is actually doing is showing whatever set of request parameters was last saved in the variables, not necessarily the given client's set of parameters. Yes, it is likely a given client will set the variables and then immediately execute the code that displays the values, but this is not guaranteed. A second request might have changed these values before they are displayed by the NotThreadSafe Servlet. To avoid the problem, the variables would better be declared inside the doGet() method. The ThreadSafe Servlet in Listing 9-9 shows how to fix the code to ensure the correct set of parameter values is always shown.
package com.jspbook;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ThreadSafe extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>ThreadSafe</title>");
out.println("</head>");
out.println("<body>");
out.println("The values are:<br>");
String value1 = request.getParameter("value1");
String value2 = request.getParameter("value2");
out.println("Value 1: "+value1+"<br>");
out.println("Value 2: "+value2+"<br>");
out.println("They are the same as submitted?");
out.println("</body>");
out.println("</html>");
}
}
From the simple example above do not think that thread safety equals declaring all variables inside a service method. This is certainly not the case, and there are many good reasons for declaring a variable common to many threads. Examples include anything placed in the session or application scopes as well as some Servlet-specific variables, such as the Hashtable used in the LinkTracker Servlet (Listing 2-6 in Chapter 2). Understand that more common than not a variable should be placed inside the appropriate service method. While sometimes inefficient, it usually ensures code is thread-safe.
When a variable needs to be shared by multiple requests, there are methods of ensuring proper synchronization. The easiest method is to simply declare the entire service method as synchronized. Applying the synchronized keyword to an entire service method effectively restricts a single Servlet to processing clients' requests one at a time. An example of the NotThreadSafe Servlet synchronized in this style is shown in Listing 9-10.
package com.jspbook;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SynchronizedNotThreadSafe extends HttpServlet {
String value1;
String value2;
public synchronized void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>NotThreadSafe!</title>");
out.println("</head>");
out.println("<body>");
out.println("The values are:<br>");
value1 = request.getParameter("value1");
value2 = request.getParameter("value2");
out.println("Value 1: "+value1+"<br>");
out.println("Value 2: "+value2+"<br>");
out.println("Are they the same as submitted?");
out.println("</body>");
out.println("</html>");
}
}
While proper synchronization is assured, in practice the preceding Servlet would have incredibly poor performance. Do not do this! A better approach to take would be using the synchronized statement only around the block of code that needs synchronizing. This approach is the preferred method of providing synchronization for objects in the scope of many threads. You should refer to the source code for the LinkTracker Servlet (Listing 2-6) for a good example of using the synchronized statement in this manner.
The Servlet API contains an interface called javax.servlet.SingleThreadModel. This is a signature interface (i.e., it has no methods to implement), which, when implemented by a Servlet, marks that Servlet as single-threaded. What does this mean? Typically, a Web server will create a single instance of a Servlet to handle all requests made to that Servlet (remember that it is possible to have multiple instances of the same Servlet, based on the servlet-name element in the deployment descriptor), which is one of the reasons why Servlets have to be thread-safe. However, when a Servlet implements javax.servlet.SingleThreadModel, the container must only allow a single thread to call this Servlet instance at any one time. On the surface this seems like a great idea. No more worries about multi-threaded access, as the Servlet is single-threaded. Unfortunately, this is not true.
The SingleThreadModel interface introduces issues without solving anything. If you use SingleThreadModel you immediately cause scalability problems in your applications. You only allow a single thread through that Servlet at any one time, and the Servlet becomes a bottleneck. Containers are free to create many instances of a SingleThreadModel Servlet to ease this problem, but some do not. A bigger issue, though, is that a Servlet that implements SingleThreadModel still has to care about threads. If a SingleThreadModel-implementing Servlet uses either the ServletContext or an HttpSession, then access to these objects must still be done in a thread-safe way. The SingleThreadModel interface only guarantees this instance of the Servlet is single-threaded. It does not stop all the threads in the server, so other Servlets or other instances of the same Servlet could (and will) access ServletContext and HttpSession. Do not use the javax.servlet.SingleThreadModel interface. It comes with a high price for no benefit, and because of the drawbacks, the interface has been deprecated in the Servlet 2.4 specification.
Both the HttpSession object and the ServletContext object are shared resources. The ServletContext object can be accessed by many Servlets or JSP at the same time. The HttpSession object can be accessed at the same time by two different requests from a client. In uses of both objects proper synchronization should be used.
The simplest solution to providing accurate session and application in state is to always wrap access to the associated object in a synchronized block. Listing 9-11 demonstrates wrapping a session access using a synchronization block.
User user = new User();
HttpSession session = request.getSession(true);
synchronized(session) {
session.setAttribute("user", user);
// other session access code
}
Note that the synchronized block is synchronized using the session object itself. This ensures that all access of the session object is done in a synchronous manner. Similarly, code that is used to manipulate the ServletContext object should include a similar synchronization block as illustrated in Listing 9-12.
ServletContext ctx = getServletContext();
synchronized(ctx)
{
User user = (User) ctx.getAttribute("user");
// other 'application' access code
}
The point to take away from this example is that all access to the ServletContext and HttpSession objects should be wrapped in an appropriate synchronized block. While a simple practice, it is important to ensure proper state is maintained.
| [ directory ] |