[ Team LiB ] Previous Section Next Section

Handling Multiple Threads in a JSP

Even though Java is a fairly thread-friendly language, many Java developers are not comfortable dealing with a threaded application. If you are developing JavaServer Pages for a production system, you need to be comfortable with threading. By default, the JSP container assumes that your JSPs are thread-safe, so it might invoke the same page from multiple threads.

If all your Java code is enclosed within the <% %> tags and you don't use any external objects, your code is probably already safe. Remember, because each Java thread has its own execution stack (where local variables are stored), any method that uses only local variables should already be thread-safe. If you do use external variables, or if you declare instance variables with the <%! %> tags, you might run into threading issues.

JspWriter Is Thread-Safe

graphics/bytheway_icon.gif

Just in case you're worried, the JspWriter class (that is, the out variable) is properly synchronized. You don't have to worry that your out.print and out.println statements will fail when multiple threads try to call them at the same time.


Listing 1.9 shows an abbreviated example of a real-life situation in which threading issues became an ugly problem. In this example, the JSP is formatting name and address information for a customer. As you look it over, see whether you can spot where the threading issues occur.

Listing 1.9 Source Code for Address.jsp
<html>
<body>

<%!
// Holders for the various portions of the address
    String firstName;
    String middleName;
    String lastName;
    String address1;
    String address2;
    String city;
    String state;
    String zip;
%>

<%
// Copy the information passed into the JSP.
    firstName = request.getParameter("firstName");
    middleName = request.getParameter("middleName");
    lastName = request.getParameter("lastName");
    address1 = request.getParameter("address1");
    address2 = request.getParameter("address2");
    city = request.getParameter("city");
    state = request.getParameter("state");
    zip = request.getParameter("zip");

// Call the formatting routine.
    formatNameAndAddress(out);
%>
</body>
</html>

<%!
// Print out the name address.
    void formatNameAndAddress(JspWriter out)
        throws java.io.IOException
    {
        out.println("<PRE>");
        out.print(firstName);
// Print the middle name only if it contains data.
        if ((middleName != null) && (middleName.length() > 0)) 
        {
            out.print(" "+middleName);
        }
        out.println(" "+lastName);
        out.println(address1);

// Print the second address line only if it contains data.
        if ((address2 != null) && (address1.length() > 0))
        {
            out.println(address2);
        }
        out.println(city+", "+state+" "+zip);
        out.println("</PRE>");
    }
%> 

Trying Out This Example

graphics/bytheway_icon.gif

If you would like to try this out, you'll need to place address.jsp in the webapps/ROOT/ subdirectory of your Tomcat installation. Then, you can exercise the example by using a URL like this:

http://localhost:8080/address.jsp?firstname=fill-in &middlename=fill-in&lastname=fill-in&address1=fill-in&address2=fill-in&city=fill-in&state=fill-in&zip=fill-in

You need to provide data in place of fill-in. This example uses the request object and HTTP parameters, which we haven't discussed yet. We'll get to them a little later in the book.


Can you see the problem? If you can't, don't feel too bad. In the real-life program, the error was not discovered until the program was being tested with multiple users. The problem is that the variables holding the name and address information are instance variables. Typically, only one instance of the servlet used to service a JSP request is loaded at one time. Each time a browser requests a JSP, the Web server spawns another thread to handle the request. It's possible to have multiple threads executing the same servlet at the same time.

In the case of Listing 1.9, the problem is that the instance variables are shared across all the threads. One thread might set all the variables, and then before it has a chance to call the formatting routine, another thread comes along and changes the values. Figure 1.4 illustrates how this happens.

Figure 1.4. Multiple threads can modify the same member variable in a JSP.

graphics/01fig04.gif

As I said before, the root of the problem is that the data is being passed to formatNameAndAddress via instance variables instead of as parameters to the method. Although it seems like an obvious thing to just pass parameters, in the real-life program from which this example was pulled, the formatting routine needed about 30 different values. It is more than a little cumbersome to pass 30 parameters around, especially if they must be passed to several different methods. One solution for this problem is to synchronize the variable assignments and the method call, as in this example:


// Copy the information passed into the JSP.
    synchronized (this)
    {
        firstName = request.getParameter("firstName");
        middleName = request.getParameter("middleName");
        lastName = request.getParameter("lastName");
        address1 = request.getParameter("address1");
        address2 = request.getParameter("address2");
        city = request.getParameter("city");
        state = request.getParameter("state");
        zip = request.getParameter("zip");

// Call the formatting routine.
        formatNameAndAddress(out);
    }

Although synchronization can prevent threading problems, it does so at the cost of performance. The server tries to use multiple threads so that it can service as many requests as possible in a short amount of time. If you suddenly create a bottleneck in which a large amount of work can be done only one thread at a time, you are defeating the performance enhancements of threading. There is nothing wrong with using the synchronized keyword here and there to prevent errors, but synchronizing all or even a majority of the work in one large block is usually not a good idea.

The solution for the problem posed in Listing 1.9 involves passing the data as method parameters rather than through instance variables. All the parameters can be encapsulated in a Java class, and the Java class can be declared within the JSP as a nested class. Listing 1.10 shows the thread-safe solution.

Listing 1.10 Source Code for AddressGood.jsp
<html>
<body>

<%
// Allocate a holder for the data.
    NameAndAddress data = new NameAndAddress();

// Copy the information passed into the JSP.
    data.firstName = request.getParameter("firstName");
    data.middleName = request.getParameter("middleName");
    data.lastName = request.getParameter("lastName");
    data.address1 = request.getParameter("address1");
    data.address2 = request.getParameter("address2");
    data.city = request.getParameter("city");
    data.state = request.getParameter("state");
    data.zip = request.getParameter("zip");

// Call the formatting routine.
    formatNameAndAddress(data, out);
%>
</body>
</html>

<%!
// The holder for the formatting data
    class NameAndAddress
    {
        public String firstName;
        public String middleName;
        public String lastName;
        public String address1;
        public String address2;
        public String city;
        public String state;
        public String zip;
    }

// Print out the name address.
    void formatNameAndAddress(NameAndAddress data, JspWriter out)
        throws java.io.IOException
    {
        out.println("<PRE>");
        out.print(data.firstName);
// Print the middle name only if it contains data.
        if ((data.middleName != null) &&
            (data.middleName.length() > 0))
        {
            out.print(" "+data.middleName);
        }
        out.println(" "+data.lastName);
        out.println(data.address1);

// Print the second address line only if it contains data.
        if ((data.address2 != null) &&
            (data.address1.length() > 0))
        {
            out.println(data.address2);
        }
        out.println(data.city+", "+data.state+" "+data.zip);
        out.println("</PRE>");
    }
%>

The example in Listing 1.10 avoids thread collisions by allocating an object to hold the data it passes to another routine. Because each thread allocates its own copy of the data object, the threads cannot overwrite one another's data.

    [ Team LiB ] Previous Section Next Section