Search the Catalog
Java Servlet Programming

Java Servlet Programming

By Jason Hunter with William Crawford
1st Edition November 1998
1-56592-391-X, Order Number: 391X
528 pages, $39.95

Sample Chapter 3: The Servlet Life Cycle


In this chapter:
The Servlet Alternative
Servlet Reloading
Init and Destroy
Single-Thread Model
Background Processing
Last Modified Times

The servlet life cycle is one of the most exciting features of servlets. This life cycle is a powerful hybrid of the life cycles used in CGI programming and lower-level NSAPI and ISAPI programming, as discussed in Chapter 1, Introduction.

The Servlet Alternative

The servlet life cycle allows servlet engines to address both the performance and resource problems of CGI and the security concerns of low-level server API programming. A servlet engine may execute all its servlets in a single Java virtual machine (JVM). Because they are in the same JVM, servlets can efficiently share data with each other, yet they are prevented by the Java language from accessing one another's private data. Servlets may also be allowed to persist between requests as object instances, taking up far less memory than full-fledged processes.

Before we proceed too far, you should know that the servlet life cycle is highly flexible. Servers have significant leeway in how they choose to support servlets. The only hard and fast rule is that a servlet engine must conform to the following life cycle contract:

  1. Create and initialize the servlet.
  2. Handle zero or more service calls from clients.
  3. Destroy the servlet and then garbage collect it.

It's perfectly legal for a servlet to be loaded, created, and instantiated in its own JVM, only to be destroyed and garbage collected without handling any client requests or after handling just one request. Any servlet engine that makes this a habit, however, probably won't last long on the open market. In this chapter we describe the most common and most sensible life cycle implementations for HTTP servlets.

A Single Java Virtual Machine

Most servlet engines want to execute all servlets in a single JVM. Where that JVM itself executes can differ depending on the server, though. With a server written in Java, such as the Java Web Server, the server itself can execute inside a JVM right alongside its servlets.

With a single-process, multithreaded web server written in another language, the JVM can often be embedded inside the server process. Having the JVM be part of the server process maximizes performance because a servlet becomes, in a sense, just another low-level server API extension. Such a server can invoke a servlet with a lightweight context switch and can provide information about requests through direct method invocations.

A multiprocess web server (which runs several processes to handle requests) doesn't really have the choice to embed a JVM directly in its process because there is no one process. This kind of server usually runs an external JVM that its processes can share. With this approach, each servlet access involves a heavyweight context switch reminiscent of FastCGI. All the servlets, however, still share the same external process.

Fortunately, from the perspective of the servlet (and thus from your perspective, as a servlet author), the server's implementation doesn't really matter because the server always behaves the same way.

Instance Persistence

We said above that servlets persist between requests as object instances. In other words, at the time the code for a servlet is loaded, the server creates a single class instance. That single instance handles every request made of the servlet. This improves performance in three ways:

Not only do servlets persist between requests, but so do any threads created by servlets. This perhaps isn't useful for the run-of-the-mill servlet, but it opens up some interesting possibilities. Consider the situation where one background thread performs some calculation while other threads display the latest results. It's quite similar to an animation applet where one thread changes the picture and another one paints the display.

A Simple Counter

To demonstrate the servlet life cycle, we'll begin with a simple example. Example 3-1 shows a servlet that counts and displays the number of times it has been accessed. For simplicity's sake, it outputs plain text.

 

Example 3-1. A simple counter 

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class SimpleCounter extends HttpServlet {
 
  int count = 0;
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
    count++;
    out.println("Since loading, this servlet has been accessed " +
                count + " times.");
  }
}

The code is simple--it just prints and increments the instance variable named count--but it shows the power of persistence. When the server loads this servlet, the server creates a single instance to handle every request made of the servlet. That's why this code can be so simple. The same instance variables exist between invocations and for all invocations.

A Simple Synchronized Counter

From the servlet-developer's perspective, each client is another thread that calls the servlet via the service(), doGet(), or doPost() methods, as shown in Figure 3-1. [1]

Figure 3-1. Many threads, one servlet instance

If your servlets only read from the request, write to the response, and save information in local variables (that is, variables declared within a method), you needn't worry about the interaction among these threads. Once any information is saved in nonlocal variables (that is, variables declared within a class but outside any specific method), however, you must be aware that each of these client threads has the ability to manipulate a servlet's nonlocal variables. Without precautions, this may result in data corruption and inconsistencies. For example, the SimpleCounter servlet makes a false assumption that the counter incrementation and output occur atomically (immediately after one another, uninterrupted). It's possible that if two requests are made to SimpleCounter around the same time, each will print the same value for count. How? Imagine that one thread increments the count and just afterward, before the first thread prints the count, the second thread also increments the count. Each thread will print the same count value, after effectively increasing its value by 2.[2] The order of execution goes something like this

count++            // Thread 1
count++            // Thread 2
out.println        // Thread 1
out.println        // Thread 2

Now, in this case, the inconsistency is obviously not a problem, but many other servlets have more serious opportunities for errors. To prevent these types of problems and the inconsistencies that come with them, we can add one or more synchronized blocks to the code. Anything inside a synchronized block or a synchronized method is guaranteed not to be executed concurrently by another thread. Before any thread begins to execute synchronized code, it must obtain a monitor (lock) on a specified class. If another thread already has that monitor--because it is already executing the same synchronized block or some other block with the same monitor--the first thread must wait. All this is handled by the language itself, so it's very easy to use. Synchronization, however, should be used only when necessary. On some platforms, it requires a fair amount of overhead to obtain the monitor each time a synchronized block is entered. More importantly, during the time one thread is executing synchronized code, the other threads may be blocked waiting for the monitor to be released.

For SimpleCounter, we have four options to deal with this potential problem. First, we could add the keyword synchronized to the doGet() signature:

public synchronized void doGet(HttpServletRequest req, 
                               HttpServletResponse res)

This guarantees consistency by synchronizing the entire method, using the servlet class as the monitor. In general, though, this is not the right approach because it means the servlet can handle only one GET request at a time.

Our second option is to synchronize just the two lines we want to execute atomically:

PrintWriter out = res.getWriter();
synchronized(this) {
  count++;
  out.println("Since loading, this servlet has been accessed " +
              count + " times.");
}

This approach works better because it limits the amount of time this servlet spends in its synchronized block, while accomplishing the same goal of a consistent count. Of course, for this simple example, it isn't much different than the first option.

Our third option is to create a synchronized block that performs all the work that needs to be done serially, then use the results outside the synchronized block. For our counter servlet, we can increment the count in a synchronized block, save the incremented value to a local variable (a variable declared inside a method), then print the value of the local variable outside the synchronized block:

PrintWriter out = res.getWriter();
int local_count;
synchronized(this) {
  local_count = ++count;
}
out.println("Since loading, this servlet has been accessed " +
            local_count + " times.");

This change shrinks the synchronized block to be as small as possible, while still maintaining a consistent count.

Our last option is to decide that we are willing to suffer the consequences of ignoring synchronization issues. Sometimes the consequences are quite acceptable. For this example, ignoring synchronization means that some clients may receive a count that's a bit off. Not a big deal, really. If this servlet were supposed to return unique numbers, however, it would be a different story.

Although it's not possible with this example, an option that exists for other servlets is to change instance variables into local variables. Local variables are not available to other threads and thus don't need to be carefully protected from corruption. At the same time, however, local variables are not persistent between requests, so we can't use them to store the persistent state of our counter.

A Holistic Counter

Now, the "one instance per servlet" model is a bit of a gloss-over. The truth is that each registered name for a servlet (but not each alias) is associated with one instance of the servlet. The name used to access the servlet determines which instance handles the request. This makes sense because the impression to the client should be that differently named servlets operate independently. The separate instances are also a requirement for servlets that accept initialization parameters, as discussed later in this chapter.

Our SimpleCounter example uses the count instance variable to track the number of times it has been accessed. If, instead, it needed to track the count for all instances (and thus all registered aliases), it can in some cases use a class, or static, variable. These variables are shared across all instances of a class. Example 3-2 demonstrates with a servlet that counts three things: the times it has been accessed, the number of instances created by the server (one per name), and the total times all of them have been accessed.

 

Example 3-2. A more holistic counter

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class HolisticCounter extends HttpServlet {
 
  static int classCount = 0;  // shared by all instances
  int count = 0;              // separate for each servlet
  static Hashtable instances = new Hashtable();  // also shared
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
 
    count++;
    out.println("Since loading, this servlet instance has been accessed " +
                count + " times.");
 
    // Keep track of the instance count by putting a reference to this
    // instance in a Hashtable. Duplicate entries are ignored. 
    // The size() method returns the number of unique instances stored.
    instances.put(this, this);
    out.println("There are currently " + 
                instances.size() + " instances.");
 
    classCount++;
    out.println("Across all instances, this servlet class has been " +
                "accessed " + classCount + " times.");
  }
}

This HolisticCounter tracks its own access count with the count instance variable, the shared count with the classCount class variable, and the number of instances with the instances hashtable (another shared resource that must be a class variable). Sample output is shown in Figure 3-2.

Figure 3-2. Output from HolisticCounter

Servlet Reloading

If you tried using these counters for yourself, you may have noticed that any time you recompiled one, its count automatically began again at 1. Trust us--it's not a bug, it's a feature. Most servers automatically reload a servlet after its class file (under the default servlet directory, such as server_root/servlets) changes. It's an on-the-fly upgrade procedure that greatly speeds up the development-test cycle--and allows for long server uptimes.

Servlet reloading may appear to be a simple feature, but it's actually quite a trick--and requires quite a hack. ClassLoader objects are designed to load a class just once. To get around this limitation and load servlets again and again, servers use custom class loaders that load servlets from the default servlets directory. This explains why the servlet classes are found in server_root/servlets, even though that directory doesn't appear in the server's classpath.

When a server dispatches a request to a servlet, it first checks if the servlet's class file has changed on disk. If it has changed, the server abandons the class loader used to load the old version and creates a new instance of the custom class loader to load the new version. Old servlet versions can stay in memory indefinitely (and, in fact, other classes can still hold references to the old servlet instances, causing odd side effects, as explained in Chapter 11, Interservlet Communication), but the old versions are not used to handle any more requests.

Servlet reloading is not performed for classes found in the server's classpath (such as server_root/classes) because those classes are loaded by the core, primordial class loader. These classes are loaded once and retained in memory even when their class files change.

It's generally best to put servlet support classes (such as the utility classes in com.oreilly.servlet) somewhere in the server's classpath (such as server_root/classes) where they don't get reloaded. The reason is that support classes are not nicely reloaded like servlets. A support class, placed in the default servlets directory and accessed by a servlet, is loaded by the same class loader instance that loaded the servlet. It doesn't get its own class loader instance. Consequently, if the support class is recompiled but the servlet referring to it isn't, nothing happens. The server checks only the timestamp on servlet class files.[3]

A frequently used trick to improve performance is to place servlets in the default servlet directory during development and move them to the server's classpath for deployment. Having them out of the default directory eliminates the needless timestamp comparison for each request.

Init and Destroy

Just like applets, servlets can define init() and destroy() methods. A servlet's init(ServletConfig) method is called by the server immediately after the server constructs the servlet's instance. Depending on the server and its configuration, this can be at any of these times:

In any case, init() is guaranteed to be called before the servlet handles its first request.

The init() method is typically used to perform servlet initialization--creating or loading objects that are used by the servlet in the handling of its requests. Why not use a constructor instead? Well, in JDK 1.0 (for which servlets were originally written), constructors for dynamically loaded Java classes (such as servlets) couldn't accept arguments. So, in order to provide a new servlet any information about itself and its environment, a server had to call a servlet's init() method and pass along an object that implements the ServletConfig interface. Also, Java doesn't allow interfaces to declare constructors. This means that the javax.servlet.Servlet interface cannot declare a constructor that accepts a ServletConfig parameter. It has to declare another method, like init(). It's still possible, of course, for you to define constructors for your servlets, but in the constructor you don't have access to the ServletConfig object or the ability to throw a ServletException.

This ServletConfig object supplies a servlet with information about its initialization (init) parameters. These parameters are given to the servlet itself and are not associated with any single request. They can specify initial values, such as where a counter should begin counting, or default values, perhaps a template to use when not specified by the request. In the Java Web Server, init parameters for a servlet are usually set during the registration process. See Figure 3-3.

Figure 3-3. Setting init parameters in the Java Web Server

Other servers set init parameters in different ways. Sometimes it involves editing a configuration file. One creative technique you can use with the Java Web Server, but currently by no other servers, is to treat servlets as JavaBeans. Such servlets can be loaded from serialized files or have their init properties set automatically by the server at load time using introspection. See the Java Web Server documentation for more information.

The ServletConfig object also holds a reference to a ServletContext object that a servlet may use to investigate its environment. See Chapter 4 Retrieving Information, for a full discussion of this ability.

The server calls a servlet's destroy() method when the servlet is about to be unloaded. In the destroy() method, a servlet should free any resources it has acquired that will not be garbage collected. The destroy() method also gives a servlet a chance to write out its unsaved cached information or any persistent information that should be read during the next call to init().

A Counter with Init

Init parameters can be used for anything. In general, they specify initial values or default values for servlet variables, or they tell a servlet how to customize its behavior in some way. Example 3-3 extends our SimpleCounter example to read an init parameter (named initial) that stores the initial value for our counter.

 

Example 3-3. A counter that reads init parameters 

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class InitCounter extends HttpServlet {
 
  int count;
 
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    String initial = config.getInitParameter("initial");
    try {
      count = Integer.parseInt(initial);
    }
    catch (NumberFormatException e) {
      count = 0;
    }
  }
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                           throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
    count++;
    out.println("Since loading (and with a possible initialization");
    out.println("parameter figured in), this servlet has been accessed");
    out.println(count + " times.");
  }
}

The init() method accepts an object that implements the ServletConfig interface. It uses the config object's getInitParameter() method to get the value for the init parameter named initial. This method takes the name of the parameter as a String and returns the value as a String. There is no way to get the value as any other type. This servlet therefore converts the String value to an int or, if there's a problem, defaults to a value of 0.

Take special note that the first thing the init() method does is call super.init(config). Every servlet's init() method must do this!

Why must the init() method call super.init(config)? The reason is that a servlet is passed its ServletConfig instance in its init() method, but not in any other method. This could cause a problem for a servlet that needs to access its config object outside of init(). Calling super.init(config) solves this problem by invoking the init() method of GenericServlet, which saves a reference to the config object for future use.

So, how does a servlet make use of this saved reference? By invoking methods on itself. The GenericServlet class itself implements the ServletConfig interface, using the saved config object in the implementation. In other words, after the call to super.init(config), a servlet can invoke its own getInitParameter() method. That means we could replace the following call:

String initial = config.getInitParameter("initial");

with:

String initial = getInitParameter("initial");

This second style works even outside of the init() method. Just remember, without the call to super.init(config) in the init() method, any call to the GenericServlet's implementation of getInitParameter() or any other ServletConfig methods will throw a NullPointerException. So, let us say it again: every servlet's init() method should call super.init(config) as its first action. The only reason not to is if the servlet directly implements the javax.servlet.Servlet interface, where there is no super.init().

A Counter with Init and Destroy

Up until now, the counter examples have demonstrated how servlet state persists between accesses. This solves only part of the problem. Every time the server is shut down or the servlet is reloaded, the count begins again. What we really want is persistence across loads--a counter that doesn't have to start over.

The init() and destroy() pair can accomplish this. Example 3-4 further extends the InitCounter example, giving the servlet the ability to save its state in destroy() and load the state again in init(). To keep things simple, assume this servlet is not registered and is accessed only as http://server:port/servlet/InitDestroyCounter. If it were registered under different names, it would have to save a separate state for each name.

 

Example 3-4. A fully persistent counter 

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class InitDestroyCounter extends HttpServlet {
 
  int count;
 
  public void init(ServletConfig config) throws ServletException {
    // Always call super.init(config) first  (servlet mantra #1)
    super.init(config);
 
    // Try to load the initial count from our saved persistent state
    try {
      FileReader fileReader = new FileReader("InitDestroyCounter.initial");
      BufferedReader bufferedReader = new BufferedReader(fileReader);
      String initial = bufferedReader.readLine();
      count = Integer.parseInt(initial);
      return;
    }
    catch (FileNotFoundException ignored) { }  // no saved state
    catch (IOException ignored) { }            // problem during read
    catch (NumberFormatException ignored) { }  // corrupt saved state
 
    // No luck with the saved state, check for an init parameter
    String initial = getInitParameter("initial");
    try {
      count = Integer.parseInt(initial);
      return;
    }
    catch (NumberFormatException ignored) { }  // null or non-integer value
 
    // Default to an initial count of "0"
    count = 0;
  }
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
    count++;
    out.println("Since the beginning, this servlet has been accessed " +
                count + " times.");
  }
 
  public void destroy() {
    saveState();
  }
 
  public void saveState() {
    // Try to save the accumulated count
    try {
      FileWriter fileWriter = new FileWriter("InitDestroyCounter.initial");
      String initial = Integer.toString(count);
      fileWriter.write(initial, 0, initial.length());
      fileWriter.close();
      return;
    }
    catch (IOException e) {  // problem during write
      // Log the exception. See .
    }
  }
}

Each time this servlet is about to be unloaded, it saves its state in a file named InitDestroyCounter.initial. In the absence of a supplied path, the file is saved in the server process' current directory, usually the server_root.[4] This file contains a single integer, saved as a string, that represents the latest count.

Each time the servlet is loaded, it tries to read the saved count from the file. If, for some reason, the read fails (as it does the first time the servlet runs because the file doesn't yet exist), the servlet checks if an init parameter specifies the starting count. If that too fails, it starts fresh with zero. You can never be too careful in init() methods.

Servlets can save their state in many different ways. Some may use a custom file format, as was done here. Others may save their state as serialized Java objects or put it into a database. Some may even perform journaling, a technique common to databases and tape backups, where the servlet's full state is saved infrequently while a journal file stores incremental updates as things change. Which method a servlet should use depends on the situation. In any case, you should always be watchful that the state being saved isn't undergoing any change in the background.

Right now you're probably asking yourself "What happens if the server crashes?" It's a good question. The answer is that the destroy() method will not be called.[5] This doesn't cause a problem for destroy() methods that only have to free resources; a rebooted server does that job just as well (if not better). But it does cause a problem for a servlet that needs to save its state in its destroy() method. For these servlets, the only guaranteed solution is to save state more often. A servlet may choose to save its state after handling each request, such as a "chess server" servlet should do, so that even if the server is restarted, the game can resume with the latest board position. Other servlets may need to save state only after some important value has changed--a "shopping cart" servlet needs to save its state only when a customer adds or removes an item from her cart. Last, for some servlets, it's fine to lose a bit of the recent state changes. These servlets can save state after some set number of requests. For example, in our InitDestroyCounter example, it should be satisfactory to save state every 10 accesses. To implement this, we can add the following line at the end of doGet():

if (count % 10 == 0) saveState();

Does this addition make you cringe? It should. Think about synchronization issues. We've opened up the possibility for data loss if saveState() is executed by two threads at the same time and the possibility for saveState() not to be called at all if count is incremented by several threads in a row before the check. Note that this possibility did not exist when saveState() was called only from the destroy() method: the destroy() method is called just once per servlet instance. Now that saveState() is called in the doGet() method, however, we need to reconsider. If by some chance this servlet is accessed so frequently that it has more than 10 concurrently executing threads, it's likely that two servlets (10 requests apart) will be in saveState() at the same time. This may result in a corrupted data file. It's also possible the two threads will increment count before either thread notices it was time to call saveState(). The fix is easy: move the count check into the synchronized block where count is incremented:

  int local_count;
  synchronized(this) {
    local_count = ++count;
    if (count % 10 == 0) saveState();
  }
  out.println("Since loading, this servlet has been accessed " +
              local_count + " times.");

The moral of the story is harder: always be vigilant to protect servlet code from multithreaded access problems.

Even though this series of counter examples demonstrates the servlet life cycle, the counters themselves aren't particularly useful because they count only the number of times they themselves have been accessed. You can find two truly useful counters--that count accesses to other pages--in the next chapter.

Single-Thread Model

Although it is standard to have one servlet instance per registered servlet name, it is possible for a servlet to elect instead to have a pool of instances created for each of its names, all sharing the duty of handling requests. Such servlets indicate this desire by implementing the javax.servlet.SingleThreadModel interface. This is an empty, tag interface that defines no methods or variables and serves only to flag the servlet as wanting the alternate life cycle.

A server that loads a SingleThreadModel servlet must guarantee, according to the Servlet API documentation, "that no two threads will execute concurrently the service method of that servlet." To accomplish this, each thread uses a free servlet instance from the pool, as shown in Figure 3-4. Thus, any servlet implementing SingleThreadModel can be considered thread safe and isn't required to synchronize access to its instance variables.

Figure 3-4. The Single Thread Model

Such a life cycle is pointless for a counter or other servlet application that requires central state maintenance. The life cycle can be useful, however, in avoiding synchronization while still performing efficient request handling.

For example, a servlet that connects to a database sometimes needs to perform several database commands atomically as part of a single transaction. Normally, this would require the servlet to synchronize around the database commands (letting it manage just one request at a time) or to manage a pool of database connections where it can "check out" and "check in" connections (letting it support multiple concurrent requests). By instead implementing SingleThreadModel and having one "connection" instance variable per servlet, a servlet can easily handle concurrent requests by letting its server manage the servlet instance pool (which doubles as a connection pool). The skeleton code is shown in Example 3-5.

 

Example 3-5. Handling database connections using SingleThreadModel 

import java.io.*;
import java.sql.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class SingleThreadConnection extends HttpServlet
                                    implements SingleThreadModel {
 
  Connection con = null;  // database connection, one per pooled servlet instance
 
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
 
    // Establish the connection for this instance
    con = establishConnection();
    con.setAutoCommit(false);
  }
 
  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
 
    // Use the connection uniquely assigned to this instance
    Statement stmt = con.createStatement();
 
    // Update the database any number of ways
 
    // Commit the transaction
    con.commit();
  }
 
  public void destroy() {
    if (con != null) con.close();
  }
 
  private Connection establishConnection() {
    // Not implemented. See .
  }
} 

Background Processing

Servlets can do more than simply persist between accesses. They can also execute between accesses. Any thread started by a servlet can continue executing even after the response has been sent. This ability proves most useful for long-running tasks whose incremental results should be made available to multiple clients. A background thread started in init() performs continuous work while request-handling threads display the current status with doGet(). It's a similar technique to that used in animation applets, where one thread changes the picture and another paints the display.

Example 3-6 shows a servlet that searches for prime numbers above one quadrillion. It starts with such a large number to make the calculation slow enough to adequately demonstrate caching effects--something we need for the next section. The algorithm it uses couldn't be simpler: it selects odd-numbered candidates and attempts to divide them by every odd integer between 3 and their square root. If none of the integers evenly divides the candidate, it is declared prime.

 

Example 3-6. On the hunt for primes 

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class PrimeSearcher extends HttpServlet implements Runnable {
 
  long lastprime = 0;                    // last prime found
  Date lastprimeModified = new Date();   // when it was found
  Thread searcher;                       // background search thread
 
  public void init(ServletConfig config) throws ServletException {
    super.init(config);                  // always!
    searcher = new Thread(this);
    searcher.setPriority(Thread.MIN_PRIORITY);  // be a good citizen
    searcher.start();
  }
 
  public void run() {
    //               QTTTBBBMMMTTTOOO
    long candidate = 1000000000000001L;  // one quadrillion and one
 
    // Begin loop searching for primes
    while (true) {                       // search forever
      if (isPrime(candidate)) {
        lastprime = candidate;           // new prime
        lastprimeModified = new Date();  // new "prime time"
      }
      candidate += 2;                    // evens aren't prime
 
      // Between candidates take a 0.2 second break.
      // Another way to be a good citizen with system resources.
      try {
        searcher.sleep(200);
      } 
      catch (InterruptedException ignored) { }
    }
  }
 
  private static boolean isPrime(long candidate) {
    // Try dividing the number by all odd numbers between 3 and its sqrt
    double sqrt = Math.sqrt(candidate);
    for (long i = 3; i <= sqrt; i += 2) {
      if (candidate % i == 0) return false;  // found a factor
    }
 
    // Wasn't evenly divisible, so it's prime
    return true;
  }
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
    if (lastprime == 0) {
      out.println("Still searching for first prime...");
    }
    else {
      out.println("The last prime discovered was " + lastprime);
      out.println(" at " + lastprimeModified);
    }
  }
 
  public void destroy() {
    searcher.stop();
  }
}

The searcher thread begins its search in the init() method. Its latest find is saved in lastprime, along with the time it was found in in lastprimeModified. Each time a client accesses the servlet, the doGet() method reports the largest prime found so far and the time it was found. The searcher runs independently of client accesses; even if no one accesses the servlet it continues to find primes silently. If several clients access the servlet at the same time, they all see the same current status.

Notice that the destroy() method stops the searcher thread.[6] This is very important! If a servlet does not stop its background threads, they continue to run until the virtual machine exits. Even when a servlet is reloaded (either explicitly or because its class file changed), its threads won't be stopped. Instead, it's likely that the new servlet will create extra copies of the background threads. And, at least with the Java Web Server, even explicitly restarting the web server service doesn't stop background threads because the Java Web Server virtual machine continues its execution.

Last Modified Times

By now, we're sure you've learned that servlets handle GET requests with the doGet() method. And that's almost true. The full truth is that not every request really needs to invoke doGet(). For example, a web browser that repeatedly accesses PrimeSearcher should need to call doGet() only after the searcher thread has found a new prime. Until that time, any call to doGet() just generates the same page the user has already seen, a page probably stored in the browser's cache. What's really needed is a way for a servlet to report when its output has changed. That's where the getLastModified() method comes in.

Most web servers, when they return a document, include as part of their response a Last-Modified header. An example Last-Modified header value might be:

Tue, 06-May-98 15:41:02 GMT

This header tells the client the time the page was last changed. That information alone is only marginally interesting, but it proves useful when a browser reloads a page.

Most web browsers, when they reload a page, include in their request an If-Modified-Since header. Its structure is identical to the Last-Modified header:

Tue, 06-May-98 15:41:02 GMT

This header tells the server the Last-Modified time of the page when it was last downloaded by the browser. The server can read this header and determine if the file has changed since the given time. If the file has changed, the server must send the newer content. If the file hasn't changed, the server can reply with a simple, short response that tells the browser the page has not changed and it is sufficient to redisplay the cached version of the document. For those familiar with the details of HTTP, this response is the 304 "Not Modified" status code.

This technique works great for static pages: the server can use the file system to find out when any file was last modified. For dynamically generated content, though, such as that returned by servlets, the server needs some extra help. By itself, the best the server can do is play it safe and assume the content changes with every access, effectively eliminating the usefulness of the Last-Modified and If-Modified-Since headers.

The extra help a servlet can provide is implementing the getLastModified() method. A servlet should implement this method to return the time it last changed its output. Servers call this method at two times. The first time the server calls it is when it returns a response, so that it can set the response's Last-Modified header. The second time occurs in handling GET requests that include the If-Modified-Since header (usually reloads), so it can intelligently determine how to respond. If the time returned by getLastModified() is equal to or earlier than the time sent in the If-Modified-Since header, the server returns the "Not Modified" status code. Otherwise, the server calls doGet() and returns the servlet's output.[7]

Some servlets may find it difficult to determine their last modified time. For these situations, it's often best to use the "play it safe" default behavior. Many servlets, however, should have little or no problem. Consider a "bulletin board" servlet where people post carpool openings or the need for racquetball partners. It can record and return when the bulletin board's contents were last changed. Even if the same servlet manages several bulletin boards, it can return a different modified time depending on the board given in the parameters of the request. Here's a getLastModified() method for our PrimeSearcher example that returns when the last prime was found.

public long getLastModified(HttpServletRequest req) {
  return lastprimeModified.getTime() / 1000 * 1000;
}

Notice that this method returns a long value that represents the time as a number of milliseconds since midnight, January 1, 1970, GMT. This is the same representation used internally by Java to store time values. Thus, the servlet uses the getTime() method to retrieve lastprimeModified as a long.

Before returning this time value, the servlet rounds it down to the nearest second by dividing by 1000 and then multiplying by 1000. All times returned by getLastModified() should be rounded down like this. The reason is that the Last-Modified and If-Modified-Since headers are given to the nearest second. If getLastModified() returns the same time but with a higher resolution, it may erroneously appear to be a few milliseconds later than the time given by If-Modified-Since. For example, let's assume PrimeSearcher found a prime exactly 869127442359 milliseconds since the beginning of the Disco Decade. This fact is told to the browser, but only to the nearest second:

Thu, 17-Jul-97 09:17:22 GMT

Now let's assume that the user reloads the page and the browser tells the server, via the If-Modified-Since header, the time it believes its cached page was last modified:

Thu, 17-Jul-97 09:17:22 GMT

Some servers have been known to receive this time, convert it to exactly 869127442000 milliseconds, find that this time is 359 milliseconds earlier than the time returned by getLastModified(), and falsely assume that the servlet's content has changed. This is why, to play it safe, getLastModified() should always round down to the nearest thousand milliseconds.

The HttpServletRequest object is passed to getLastModified() in case the servlet needs to base its results on information specific to the particular request. The generic bulletin board servlet can make use of this to determine which board was being requested, for example.


1. Does it seem confusing how one servlet instance can handle multiple requests at the same time? If so, it's probably because when we picture an executing program we often see object instances performing the work, invoking each other's methods and so on. But, although this model works for simple cases, it's not how things actually work. In reality, all real work is done by threads. The object instances are nothing more than data structures manipulated by the threads. Therefore, if there are two threads running, it's entirely possible that both are using the same object at the same time.

2.Odd factoid: if count were a 64-bit long instead of a 32-bit int, it would be theoretically possible for the increment to be only half performed at the time it is interrupted by another thread. This is because Java uses a 32-bit wide stack.

3. For the daredevils out there, here's a stunt you can try to force a support class reload. Put the support class in the servlet directory. Then convince the server it needs to reload the servlet that uses the support class (recompile it or use the Unix utility touch). The class loader that reloads the servlet should also load the new version of the support class.

4. The exact location of the current user directory can be found using System.getProperty("user.dir").

5. Unless you're so unlucky that your server crashes while in the destroy() method. In that case, you may be left with a partially-written state file--garbage written on top of your previous state. To be perfectly safe, a servlet should save its state to a temporary file and then copy that file on top of the official state file in one command.

6. Stopping threads using the stop() method as shown here is deprecated in JDK 1.2 in favor of a safer flag-based system, where a thread must periodically examine a "flag" variable to determine when it should stop, at which point it can clean up and return from its run() method. See the JDK documentation for details. Example source code can be found in an article titled "Scott's Solutions: Programming with threads in Java 1.2", written by Scott Oaks for Java Report Online, found at http://www.sigs.com/jro/features/9711/oaks.html.

7. A servlet can directly set its Last-Modified header inside doGet(), using techniques discussed in Chapter 5 Sending HTML Information. However, by the time the header is set inside doGet(), it's too late to decide whether or not to call doGet().


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.