Book HomeJava and XSLTSearch this book

6.5. Servlet Threading Issues

Like it or not, a servlet must be capable of serving more than one client at a time. Built-in threading capability is one of the key reasons why Java is so well-suited to server applications, particularly when compared to a traditional CGI model. As usual, however, tradeoffs are involved. In particular, writing code that can handle many concurrent tasks without corrupting data can be quite challenging at times. Ideally, this material can alert you to the most common causes of threading problems found in a servlet environment.

6.5.1. Servlet Threading Model

In the standard servlet model, a client makes a request via the servlet's service( ) method. In the HttpServlet class, the service( ) method determines the type of HTTP request and delegates to methods such as doGet( ) or doPost( ). If several clients issue requests at the same time, these methods will serve each client in a different thread. Since most servlets are subclasses of HttpServlet, your main concern is insuring that service( ), doGet( ), and doPost( ) can handle many concurrent clients.

Before handling any requests, a servlet's init( ) method is invoked. According to the servlet API specification, this method must be invoked by only a single thread and must complete successfully before subsequent threads are allowed to enter the service( ) method. For this reason, you do not have to worry about threading problems inside of the init( ) method. From there, however, all bets are off.

One simplistic approach to thread safety is to declare a method as synchronized. In this approach, your doGet( ) method would be declared as follows:

protected synchronized void doGet(HttpServletRequest request,
        HttpServletResponse response) throws IOException, ServletException {
    ...
}

The synchronized keyword will require that any thread wishing to invoke this method first obtain a lock on the servlet object. Once the first client obtains the lock and begins to execute the method, all others must wait their turn. If the doGet( ) method takes 0.5 seconds to execute, then a load of a mere 100 users will result in nearly a minute-long wait for many visitors to your site, since each waits in a queue for access to the lock.

This is almost never a viable option, so another choice is to declare that your servlet implements the javax.servlet.SingleThreadModel interface as follows:

public class MyServlet extends HttpServlet implements SingleThreadModel {
...
}

The SingleThreadModel interface is a marker interface, meaning that it does not declare any methods. It merely indicates to the servlet container that your servlet is not thread-safe, and can handle only one request at a time in its service( ) method. A typical servlet container will maintain a pool of servlet instances in this case, allowing each instance to handle a single request at a time.

This is somewhat better than merely synchronizing the doGet( ) or doPost( ) method. However, it does mean that multiple copies of the servlet will be instantiated. This results in higher memory overhead and still does not ensure that all threading issues will be resolved. For example, concurrent modifications to a shared resource such as a file or a static field are not prevented in any way.

6.5.2. Thread Safety Tips

Most servlet threading problems occur when two or more threads make changes to the same resource. This might mean that two threads try to modify a file, or perhaps several threads all update the value of a shared variable at the same instant. This causes unpredictable behavior and can be very hard to diagnose. Another type of thread problem is deadlock, where two threads are in contention for the same resource, each holding a lock that the other thread needs. Yet another problem is performance. As mentioned earlier, synchronizing access to a method can introduce significant performance penalties.

The best overall approach to servlet thread safety is to avoid the SingleThreadModel interface and synchronizing access to the service( ) method. This way, your servlet can handle multiple client requests at the same time. This also means that you must avoid situations where more than one thread can modify a shared resource concurrently. The following tips should offer some guidance.

6.5.2.1. Tip 1: Local variables are thread-safe

Object fields in a servlet are often bad news. Consider the following code:

public class HomeServlet extends HttpServlet {
    private Customer currentCust;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws IOException,
            ServletException {
        HttpSession session = request.getSession(true);
        currentCust = (Customer) session.getAttribute("cust");
        currentCust.setLastAccessedTime(new Date( ));
        ...
    }
}

In this code, the currentCust field is obtained from the HttpSession whenever a client enters the doGet( ) method. Unfortunately, if another thread invokes this method an instant later, the currentCust field will be overwritten before the first thread is complete. In fact, dozens of threads could enter the doGet( ) method at roughly the same time, repeatedly replacing the currentCust reference. This would lead to complete failure of this servlet.

The easy fix is to make currentCust a local variable as follows:

public class HomeServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws IOException,
            ServletException {
        HttpSession session = request.getSession(true);
        Customer currentCust = (Customer) session.getAttribute("cust");
        currentCust.setLastAccessedTime(new Date( ));
        ...
    }
}

This fixes our problem because each thread gets its own copy of local variables in Java. By simply removing the object field and replacing it with a local variable, this particular threading problem is resolved.

6.5.2.2. Tip 2: Immutable objects are thread-safe

Whenever two or more threads make changes to the same object at the same time, a race condition can occur. Consider the following code:

public class Person {
    private String firstName;
    private String lastName;

    public void setName(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    ...getter methods omitted
}

If two threads invoke the setName( ) method at roughly the same time, the following scenario can occur:

  1. Thread "A" sets the first name to "Bill," but is interrupted by thread "B".

  2. Thread "B" sets the first and last names to "George" and "Bush."

  3. Thread "A" regains control, and sets the last name to "Clinton."

At this point, the person's name is George Clinton, which is clearly not what was intended. Although you could make the setName( ) method synchronized, you would also have to make any get methods synchronized as well.

Another option is to make this an immutable object. An immutable object cannot be modified, so multiple threads cannot concurrently alter it. The Person class can be modified as follows:

public class Person {
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName( ) { return this.firstName; }
    public String getLastName( ) { return this.lastName; }
}

Since instances of the Person class cannot be modified, its methods do not have to be synchronized. This makes the objects fast and allows them to be read by many threads concurrently. The only drawback is that you cannot make changes to these objects once they are constructed. The simple fix is to create a brand new Person object whenever a change needs to be made. This is essentially the approach that java.lang.String takes.

Immutable objects are not always an option but can be a useful technique for many smaller "data helper" classes that seem to pop up in every application.

6.5.2.3. Tip 3: Provide a single point of entry

When dealing with a single instance of a shared resource, such as a file that needs to be modified, you should consider creating a facade around that resource. This is a single class that provides controlled access to that resource, thus providing a single point in your code for proper synchronization. The following code snippet illustrates how you can essentially create a facade around a data source that holds Customer objects. It is assumed that the Customer class is immutable, making it impossible to change a Customer instance without going through this well-defined API:

public class CustomerSource {
    public static synchronized Customer getCustomer(String id) {
        // read the customer from a file, or perhaps
        // from a database...
    }

    public static synchronized Customer createCustomer( ) {
        // create a new customer in the file or database
        // and return it...
    }

    public static synchronized void deleteCustomer(String id) {
        // ...
    }
}

This is just one simple approach that works best on smaller applications. A servlet's doGet( ) or doPost( ) method should utilize the CustomerSource class without any data corruption. If the methods in CustomerSource are slow, however, they will hinder scalability as more and more clients wait for their turn to access the underlying data source.

6.5.2.4. Tip 4: Understand the Templates interface

Multiple threads can share implementations of javax.xml.transform.Templates. Therefore, instances can be stored as object fields on a servlet:

public class MyServlet extends HttpServlet {
    private Templates homePageStylesheet;

    ...
}

But instances of javax.xml.transform.Transformer are not thread-safe; they should be declared as local variables within the doGet( ) or doPost( ) method:

public class MyServlet extends HttpServlet {
    private Templates homePageStylesheet;

    public void init( ) throws UnavailableException {
        ... create the Templates instance
    }

    protected void doGet( ) {
        Transformer trans = homePageStylesheet.newTransformer( );
        ... use this Transformer instance, a local variable
    }
}


Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.