Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Developing Professional Java Applets

Chapter 8 -- Adding Threads to Applets

Chapter 8

Adding Threads to Applets


CONTENTS




Using images might make your applet look nicer, but it also slows the performance of your applet quite a bit. The biggest reason for this delay is that images are large compared to normal text documents, such as HTML files; consequently, images take longer to load from the network. Another reason for this delay is the time it takes to render some images. Most images are based on a graphics format that uses some type of compression algorithm. Although this compression can result in faster delivery, rendering the image will take additional time, even though this time delay may be minor.

In traditional programming, you would have no options for speeding up the image display. You would simply have to wait for each image to be downloaded, then wait for it to be rendered. However, this chapter ends Part III of this book by introducing multithreading as a way to make your applet run faster and more efficiently. No longer will you have to wait for each image to be downloaded and rendered. With multithreading, you can download images while other images are being displayed. The prospective user of your applet won't have to wait for all the images to be downloaded to make a decision. With multithreading, you can use a single image when it's ready instead of waiting for all images to be made available.

What Is a Thread?

A thread has many features of a standalone process. It has a beginning, a middle, and an ending. Technically speaking, a thread is a single sequential stream of execution. A thread can perform a task until it has run to its conclusion (such as a long calculation), or it can run indefinitely, waiting for requests to perform a service (such as a database server). In practical terms, a thread can do just about everything a process can.

So what is special about threads? The key thing to note is that a thread runs inside a process; to put it another way, a process can contain one or more threads (in an operating environment that supports multithreading). This means that one process can have a thread that performs a calculation, another thread that performs a service, and so forth. Furthermore, threads can create new threads. In Java, threads are everywhere. There are even threads you haven't explicitly created. The object garbage collector runs in the virtual machine as a thread. Even the Applet class that starts your applet is itself running as part of a thread.

There are other subtle differences between processes and threads. Multitasking operating systems, which have been around for a long time, allow multiple processes to run simultaneously. However, these processes are generally completely independent of each other; the only thing they share is the operating environment. Consequently, activities like getting processes to communicate with each other can be a little tricky. In a multithreading environment, however, programs that would traditionally be run as separate processes can be structured to run as separate threads within a common process. This allows the threads to share resources common to that process, such as objects and variables. Consequently, the threads of a process, though running separately, also work together to form a greater whole. Each thread can be seen as existing as part of a single, greater unit. For example, when the process dies, so do all its threads.

Creating a Thread with the Thread Class

Java offers a variety of tools for writing multithreaded programs. The most useful is the Thread class, which, as its name suggests, is used for creating a class that runs as a thread. You need to declare a subclass of Thread if you want to create a functional thread. After the subclass is created, you need to override Thread's run() method. This method is the key to making the Thread class perform as a thread; it defines your class's thread of execution. After the run() method is established and your class is instantiated, you can begin the thread with the start() method.

A simple example shows how easy it is to create a thread in Java. Figure 8.1 shows an applet with lines drawn randomly throughout its workspace. These lines are drawn by a thread using the code in Listing 8.1. The class LineThread is a subclass of Thread that loops indefinitely, randomly drawing lines on an applet. The applet is provided to LineThread through its constructor. Although the LineThread class has a custom constructor, Thread classes often do not have special constructors.

Figure 8.1 : An applet with lines drawn by a thread


Listing 8.1. The source code for applet with lines drawn by a thread.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;

// This is an applet that creates a Thread that
// randomly draws lines on its workspace...
public class LineApplet extends Applet  {
   Thread t;
   // Set the size of the applet, create the thread,
   // and start it...
   public void init() {
      resize(300,300);
      t = new LineThread(this);
      t.start();
   }

   // Click the mouse to kill the thread...
   public boolean mouseDown(Event ev, int x1, int x2) {

      if (t != null) {
         t.stop();
         t = null;
      }
      return true;
   };
}

// Thread that randomly draws lines all over a component...
class LineThread extends Thread {
   Applet a;  // Thread needs to know the applet...
   // Constructor simply stores the applet to paint...
   public LineThread(Applet a) {
    this.a = a;
   }

   // Run the thread. Lines everywhere!
   public void run() {
      // Get dimension data about the applet...
      double width = (double) a.size().width;
      double height = (double)a.size().height;
      // Loop and draw lines forever...
      while (true) {
         Graphics g = a.getGraphics();
         g.drawLine((int)(width * Math.random()),
            (int)(height * Math.random()),
            (int)(width * Math.random()),
            (int)(height * Math.random()) );
      }
   }
}

The run() method of LineThread provides the applet's threaded activity. After getting the applet's dimensions, the thread enters an indefinite while loop, randomly drawing lines on each iteration. (The Component class's getGraphics() method is a way of getting a Graphics object to draw on a component outside the paint() method.) The thread will run until the applet is terminated or until the thread is explicitly stopped.

After setting its own size and constructing a LineThread object, the Applet class, LineApplet, begins the line drawing by calling the start() method. This start() method call begins the thread and invokes the run() method of the LineThread class. The thread will not run until the start() method is applied.

The stop() method terminates a thread. In a general sense, stop() causes a thread to abruptly leave its run() method and, therefore, end its threaded activity. In this example, a mouse click on the applet causes the thread (and hence the line drawing) to stop.

Enhancing Your First Multithreaded Applet

A couple of things can be done to this applet to further illustrate the Thread class. Listing 8.2 gives the source code for the enhanced version of the applet. The threaded class, LineThread, was modified to delay a little between the line drawings by using the sleep() method, which causes the thread to "sleep" for a specific number of milliseconds; in this case, the delay is for a tenth of a second. Note how the sleep() method requires catching InterruptedException objects. This is meant for situations in which a thread has been interrupted by another thread. However, this capability doesn't seem to be active in the current version of the JDK. Consequently, you will see null handlers for InterruptedException objects throughout this book. Because it is annoying to have to have an exception handler every time you call sleep(), you might want to encapsulate sleep() and the exception handling in your own method. This technique appears in the code used elsewhere in this book.


Listing 8.2. Enhanced version of the line-drawing applet.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;

// This is an applet that creates a Thread that
// randomly draws lines on its workspace...
public class LineApplet extends Applet  {
   Thread t;
   boolean running = false;
   // Set the size of the applet, create the thread
   // and start it...
   public void init() {
    resize(300,300);
    t = new LineThread(this);
    t.start();
    running = true;
   }

   // Click the mouse down to kill the thread...
   public boolean mouseDown(Event ev, int x1, int x2) {
    // If the thread is active, suspend the thread
    // and remove the lines...
    if (running) {
     running = false;
     t.suspend();
     repaint();  // Removes the lines...
    }
    // If thread is suspended, then reactivate it...
    else {
     running = true;
     t.resume();
    }
    return true;
   };

   // Destroy the thread when the applet shuts down...
   public void destroy() {
    // Stop the thread and wait for it to die...
    t.stop();
    try {
       t.join();
    }
    catch (InterruptedException e) { }
   }
}

// Thread that randomly draws lines all over a component...
class LineThread extends Thread {
   Applet a;  // Thread needs to know the applet...
   // Constructor simply stores the applet to paint...
   public LineThread(Applet a) {
  this.a = a;
   }

   // Run the thread. Lines everywhere!
   public void run() {
    // Get dimension data about the applet...
    double width = (double) a.size().width;
    double height = (double)a.size().height;
    // Loop and draw lines forever...
    while (true) {
     Graphics g = a.getGraphics();
     // Randomly select a color...
     Color c = new Color((int)(255.0 * Math.random()),
      (int)(255.0 * Math.random()),
      (int)(255.0 * Math.random()) );
     g.setColor(c);
     g.drawLine((int)(width * Math.random()),
      (int)(height * Math.random()),
      (int)(width * Math.random()),
      (int)(height * Math.random()) );
     // Sleep some...
     try {
      sleep(100);
     }
     catch (InterruptedException e) { }
    }
   }
}

A visual enhancement also was made to LineThread. It randomly creates a new Color object and uses it (with the Graphics class setColor() method) in each iteration. Figure 8.2 shows the enhancement.

Figure 8.2 : Second version of the linedrawing applet

Most of the changes occur in the Applet class LineApplet. The method that traps the mouse clicks, mouseDown(), is changed so it no longer stops the thread. It now toggles between pausing and resuming the thread's execution. The suspend() method suspends a thread's execution; when a thread is suspended, it will not continue its stream of execution until the complementary resume() method is invoked. In this example, suspending a thread is accompanied by clearing out the applet's drawing area through the repaint() method. (This clearing occurs because the applet does not override the paint() method.) The running variable is used to keep track of whether the thread is suspended; unfortunately, there is no method in the Thread class for determining this.

The last bit of code occurs in the newly added destroy() method, which is called when the applet shuts down. In this call, the stop() method terminates the thread. It is accompanied by a join() call, which forces the calling thread to wait until the victim thread is really "dead." This is important because stopping a thread does not cause an immediate (in the strict sense of the word) termination of the thread. There may be some delay due to time-slicing or some other reason. It is often dangerous not to wait for a thread to die. In some cases, the thread may run even after some of the objects it uses (like the applet) are no longer valid. This will cause an exception. Furthermore, an applet that has been stopped without waiting for its constituent threads to terminate could cause the applet to unexpectedly "hang." Consequently, it's good practice to follow stop() calls by a complementary call to join() to prevent this from happening. It should be noted that the Thread class has two additional versions of join() that take time-out flags. Use these if your applet has some severe time constraints and you cannot afford to wait indefinitely for a thread to end. The time-out values (like most time-outs in Java) are in milliseconds with an optional nanosecond precision value.

It is easy to modify the example to have multiple line-drawing threads. If you add the following code, there will be two threads drawing lines on the applet:

Thread t2 = new LineThread(this);
t2.start();

You should add corresponding stop() and join() methods to terminate the threads in the applet destroy() method.

The Runnable Interface

One thing may strike you as troubling in this discussion about how threads work. If you have to use the Thread class every time you need to run something as a thread, don't you lose the capability to inherit other classes? Because Java is a single-inheritance system, forcing a class to inherit the Thread class would seem to be overly restrictive because you couldn't, for example, run the Applet class as a thread. Fortunately, Java actually implements threads through an interface, thus allowing any class to use a thread's functions. The interface is called Runnable and has only one method, run(). The run() method of the Thread class is itself an implementation of Runnable.

The typical way of starting a class that implements Runnable as a thread is to call an alternate Thread constructor. This constructor takes a Runnable class as its target parameter. For example, if you have a Runnable object assigned to the variable r, you would pass it to the Thread constructor as follows:

t = new Thread(r);

When the thread's start() method is called after this, the run() method of the Runnable target is invoked. If you don't want to explicitly declare a Thread variable, you can start the Runnable target in a more concise manner:

new Thread(r).start();

A Thread object created with a Runnable target behaves similarly to the thread behavior discussed in the previous sections. The stop() method terminates the thread, suspend() and resume() are used to pause and restart the thread, and so on.

The line-drawing applet can be reworked to use the Runnable interface. In the example shown in Listing 8.3, the LineThread class is removed, and the Applet class (called LineRun, in this case) implements the Runnable interface. Therefore, there is only one class in the applet that both draws the lines and runs as a thread.


Listing 8.3. The line-drawing applet using the Runnable interface.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;

// Runs the Applet as a Thread so that it can paint
// lines indefinitely...
public class LineRun extends Applet implements Runnable {
   Thread t;  // This is the thread!
   // Set the size of the applet...
   public void init() {
         resize(300,300);
   }

   // Entering the Applet. Start the thread...
   public void start() {
     if (t == null) {
      t = new Thread(this);
      t.start();
     } // end if
   }

   // Leaving the Applet. Stop the thread...
   public void stop() {
     if (t != null) {
      t.stop();
      try {
         t.join();
      }
      catch (InterruptedException e) { }
     } // end if
     t = null;
   }

   // Run the thread. Lines everywhere!
   public void run() {
      // Get dimension data about the applet...
      double width = (double)size().width;
      double height = (double)size().height;
      // Loop and draw lines forever...
      while (true) {
         Graphics g = getGraphics();
         // Randomly select a color...
         Color c = new Color((int)(255.0 * Math.random()),
            (int)(255.0 * Math.random()),
            (int)(255.0 * Math.random()) );
         g.setColor(c);
         g.drawLine((int)(width * Math.random()),
            (int)(height * Math.random()),
            (int)(width * Math.random()),
            (int)(height * Math.random()) );
         // Sleep some...
         try {
            t.sleep(100);
         }
         catch (InterruptedException e) { }
      }
   }
}

There are a couple of interesting things to note about this applet. It looks almost exactly like the example in the previous section. In fact, the run() method of Listing 8.3 is only slightly modified from that of the second version of the LineThread class; the only changes are the removal of the references to the Applet class and how the sleep() method is invoked.

The thread that runs the Runnable target is constructed in the start() method of the LineRun class. (Keep in mind that the start() method here is that of the Applet class and not the Thread class.) You must check whether the thread already exists because you might move back and forth from the line-drawing Web page to another page. If you leave the drawing page, the Applet stop() method kills the thread and waits for it to die. It will be restarted if you return to the page.

An interesting thing to note about classes that implement Runnable is that they are not "dead" if their run() method has been stopped. Even if the thread is dead, the other methods in a class using Runnable still can be invoked. For example, add the following code to the LineRun class:

// Toggle the running of the line-drawing thread...
public boolean mouseDown(Event ev, int x, int y) {
     // Stop the thread if it is running...
     if (t != null) {
         t.stop();
         try {
            t.join();
         }
         catch (InterruptedException e) { }
         t = null;
     }
     // If it is dead, then start a new thread...
     else {
         t = new Thread(this);
         t.start();
     }
     return true;
};

This code stops the thread if it is running and restarts it if the thread is stopped. Therefore, the LineRun object is alive regardless of whether the run() method is active. This example points out an advantage of using the Runnable interface over creating a subclass of Thread. When a Thread object's run() method is stopped, it cannot be restarted; you have to create a new Thread to have its run() method run again. On the other hand, an object using Runnable can have its run() method repeatedly started and stopped.

Another advantage of using the Runnable interface for your custom thread is that it does carry all the baggage of the Thread class, which supports over two dozen methods. It seems wasteful to subclass threads so that you can override only the run() method. On the other hand, the Thread class might be useful if your threaded class doesn't need to be a subclass of a complex class like Applet. In general, it's best to follow the design versus implementation rule: If your class is designed to be a special kind of thread, then use the Thread class; if it's designed to be something else (like an AWT component) but needs to implement threads, then use Runnable.

Synchronization

As you can see from the previous examples, writing threaded classes in Java is generally pretty easy, so you might wonder why, until recently, relatively few languages and programs have supported multithreading. The reason for this is that multithreading is not without risks. In particular, a class meant to run in a concurrent environment must be designed so that multiple threads can access an instance of the class without causing undesired behavior. An instance of the class must be as reliable in an environment with multiple threads of execution as it is within a single-threaded environment. In other words, the class must be thread-safe. You will now see an example of a class that's not thread-safe.

A TestStack Class That Is Not Thread-Safe

Listing 8.4 shows the code for a class that is a stack of Integer objects. It is a generally well-constructed class, showing good Java programming practices, such as handling errors (like an empty stack) by throwing exceptions. However, it is not thread-safe. Why is this? Look at the push() and pop() methods. They both use the top integer variable to indicate the current top of the stack. But what happens if one thread is calling push() at the same time another thread is calling pop()? In this situation, push() and pop() might behave in an undesired fashion.


Listing 8.4. A TestStack class that isn't thread-safe.
// Classes of stack exceptions...
class StackException extends Exception { }
class StackFullException extends StackException { }
class StackEmptyException extends StackException { }

// This class implements a simple stack as
// an array of integers...
class TestStack {
   Integer s[];  // The stack array of integer objects...
   int top; // The current top of the stack. Next item to place.
   // Construct a stack of the specified size...
   public TestStack(int size) {
      s = new Integer[size];
      top = -1;   // Empty stack...
   }
   // Push an item onto the stack...
   public void push(Integer item) throws StackFullException {
      // Throw exception if stack is full...
      if (top == s.length)
            throw new StackFullException();
      // Otherwise increment the top and add the item...
      ++top;
      s[top] = item;
   }

   // Pop the top item off the stack...
   public Integer pop() throws StackEmptyException {
      // Throw exception if stack is empty...
      if (top < 0)
            throw new StackEmptyException();
      // Otherwise, return the top item and decrement the top...
      Integer I = s[top];
      s[top] = null;
      -top;
      return I;
   }

   // List the contents of the stack...
   public void list() {
      for (int i = 0; i <= top; ++i)
            System.out.println(i + ": " + s[i]);
   }
}

It is important to note that threads generally function by time-slicing. What occurs in an environment that supports time-slicing is that threads share CPU time while executing their respective code. In effect, although the threads are said to be running simultaneously, there is really only one thread executing at a given moment in time. It just seems like the threads are running simultaneously. The speed of the computer and the small increments of time make it possible for the threads to look like they're performing simultaneously.

Suppose there are two threads, A and B. If thread A is executing a method, thread B could get a slice of time before A is finished with its method. If the method is thread-safe, it won't matter to thread A that one or more threads interrupts its processing. Regardless of the time-slicing sequence, a thread-safe thread A will get the desired results.

Note
Java works best in environments designed to support preemptive time scheduling. A preemptive scheduler gives running tasks (processes or threads) small portions of time to execute by using time-slicing, discussed in this section. UNIX and Windows 95 are two operating systems that support some form of preemptive scheduling. However, some platforms, such as Windows 3.1, support a more primitive form of scheduling, called nonpreemptive. In this form of scheduling, one task doesn't give another task a chance to run until it's finished or has manually yielded its time. This makes writing cooperative programs fairly difficult-especially those with long CPU-intensive operations. You basically have to code your application around the system being nonpreemptive; in certain portions of the code, you need to call some kind of yield method to let other threads execute. In preemptive scheduling environments, this kind of manual coding is not necessary.

With time-slicing in mind, it isn't difficult to expose the problem with the TestStack class. Think about what would happen if the push() and pop() code shared time slices in the following manner:

PUSH: ++top;
POP: Integer I = s[top];
POP: s[top] = null;
POP; -top;
PUSH: s[top] = item;

In this case, the pop() call would be in error because it would return a stack top that hasn't been assigned a value yet. In short, if the push() and pop() operations share slices of time, they will probably not work properly. The operations with the top variable need to be atomic, which effectively means that another thread cannot interrupt the execution of the operation until it's finished.

This issue of operations causing undesired behavior because of concurrency is often referred to as the producer/consumer problem. This problem is characterized by one thread producing data (in this case, pushing an item onto the stack), while a concurrent thread is consuming the data (popping the stack). The problem is one of synchronization: Consuming and producing operations cannot be interleaved indiscriminately; rather, the operations need to be synchronized to guarantee thread-safe behavior.

Note
Although a stack class was used in this example, note that there is a stack class available in the java.util package. The stack class written in this chapter is used strictly to illustrate problems with multithreading. Also note that the Exception classes associated with the stack code of this section give you a good example of how to create a subhierarchy of Exception classes. In this case, a new hierarchy of stack exceptions was created, with StackException as the root.

Listing 8.5 provides Applet and Thread classes that use the TestStack class appearing throughout this discussion on synchronization. The Applet class, StackApplet, pushes an element onto the stack whenever you click the mouse. It follows this push with a listing of the current stack contents. The Thread class, called StackThread, loops indefinitely, looking for items on the stack. If one is found, its integer value is displayed to standard output. If not (indicated by a StackEmptyException object being thrown), the thread sleeps a little and tries again. The synchronization problem can be seen by clicking the mouse rapidly. The push(), pop(), and list() methods can then be interleaved. It is likely you'll see the listing appearing incorrectly if you click fast enough. In fact, the problem will be blatant: The output from the pop() method-which changes the top variable also used in list()-will occur in the middle of the list() method, thus undermining its results.


Listing 8.5. Applet and Thread classes that use the TestStack class.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;

public class StackApplet extends Applet  {
   TestStack s;
   int counter = 0;  // For stack test data...
   Thread t; // The stack thread...
   // Create the stack at initialization...
   // Make a thread to read the stack...
   public void init() {
      s = new TestStack(20);
      t = new StackThread(s);
      t.start();
   }

   // Add an item to the stack whenever you click
   // on the mouse...
   public boolean mouseDown(Event ev, int x, int y) {
      try {
         s.push(new Integer(++counter));
         s.list();
      }
      catch (StackFullException e) {
         System.out.println("Stack full!");
      }
      return true;
   };

   // Kill the stack thread when leaving...
   public void destroy() {
      t.stop();
      try {
         t.join();
      }
      catch (InterruptedException e) { }
   }
}

// Thread that constantly reads the stack and prints out
// the current top...
class StackThread extends Thread {
   TestStack s;
   public StackThread(TestStack s) {
      this.s = s;
   }
   // Loop forever, looking at the top of the stack...
   public void run() {
      Integer Top;
      while (true) {
         // Print out top of stack, if stack isn't empty...
         try {
               Top = s.pop();
               System.out.println("Thread: Read " + Top);
         }
         // Sleep some if stack is empty...
         catch (StackEmptyException se) {
               try {
                  sleep(250);
               }
               catch (InterruptedException e) { }
         }
      }
   }
}

Introducing the Synchronized Modifier

The developers of Java are aware of the synchronization problem described in the preceding section. They also know that synchronization is often dealt with by issuing many lines of code. Fortunately for Java developers, the Java architects took advantage of programming constructs introduced over 20 years ago that make creating thread-safe classes relatively easy. Java synchronization is based on the concept of monitors. The idea is that each class and object has its own monitor that functions as a lock on the item. For example, if one thread locks an object's monitor, then another thread cannot access that object until the monitor is released. A monitor can make a code fragment behave like a critical section, with a piece of code that should only have one thread in it at a given time.

The Java language's synchronized modifier is used to implement a monitor. The modifier can be applied on a method or a block of code. If a method is declared as synchronized, then a thread that invokes the method owns the monitor of the object or class until it leaves the method. If another thread tries to invoke the method while the other thread owns the monitor, then that calling thread will have to wait until the first thread is finished. When this occurs, the monitor is released, and the next thread can execute the method.

Placing the synchronized modifier in just three areas of the TestStack class is all that's needed to make that class thread-safe. To do this, the three method declarations of the class need to be redeclared, as follows:

public synchronized void push(Integer item) throws StackFullException
public synchronized Integer pop() throws StackEmptyException
public synchronized void list()

With that simple change, the TestStack class is now thread-safe!

Another way of using the synchronized modifier is to apply it to blocks of code. Specifically, the critical sections of code should be synchronized. The TestStack class can be modified to do this, as shown in Listing 8.6. The same methods from the previous declaration are modified to have blocks of the structure

synchronized(this) {
}

around their critical sections of code. The this of the synchronized statement refers to the instance of the TestStack class. The effect in this case is similar to that of synchronizing methods; no thread can enter synchronized code while another thread holds the monitor of the object. As in synchronizing methods, this version of the TestStack class is thread-safe. Note, however, that the code is a little more cumbersome and harder to read. In general, synchronizing methods is preferred over synchronizing code blocks for both readability and ease of use. Furthermore, method-based synchronization is considered more object-oriented because it more
effectively "hides" the fact that the code is executing as a thread, thus allowing you to focus on the object's behavior and not on how it works in a concurrent atmosphere. On the other hand, synchronizing blocks is more efficient because the monitors are held for shorter periods of time. If your code has only a small section of code that is critical, then it might be best to synchronize that block of code. However, classes such as TestStack are probably best implemented with synchronized methods.


Listing 8.6. The TestStack class with synchronized code blocks.
// This class implements a simple stack as
// an array of integers...
class TestStack {
   Integer s[];  // The stack array of integer objects...
   int top; // The current top of the stack. Next item to place.
   // Construct a stack of the specified size...
   public TestStack(int size) {
      s = new Integer[size];
      top = -1;   // Empty stack...
   }
   // Push an item onto the stack...
   public void push(Integer item) throws StackFullException {
    synchronized(this) {
      // Throw exception if stack is full...
      if (top == s.length)
            throw new StackFullException();
      // Otherwise increment the top and add the item...
      ++top;
      s[top] = item;
    }
   }

   // Pop the top item off the stack...
   public Integer pop() throws StackEmptyException {
    Integer I;
    synchronized(this) {
      // Throw exception if stack is empty...
      if (top < 0)
            throw new StackEmptyException();
      // Otherwise, return the top item and decrement the top...
      I = s[top];
      s[top] = null;
      -top;
    }
    return I;
   }

   // List the contents of the stack...
   public void list() {
    synchronized(this) {
      for (int i = 0; i <= top; ++i)
            System.out.println(i + ": " + s[i]);
    }
   }
}

The code blocks in Listing 8.6 could just have easily been synchronized with the stack Integer array s. The results would be the same. However, you cannot synchronize with the integer variable top. The synchronized modifier works only with objects and classes; variables, such as integers, are not proper synchronization types.

Notify and Wait

Suppose that you wanted to change the TestStack class to be blocking. That is, if you are invoking the pop() method and there is nothing on the stack, then you should wait until some other thread adds an element to the stack. However, because the pop() method is synchronized, you cannot wait inside that method without locking out the thread that is going to add the element you need! What you would like to do is wait inside the pop() method but relinquish the TestStack object monitor so that other threads can use the stack.

Fortunately, the designers of Java come to your rescue again! The base Object class has several methods built into it for this situation. Because these methods are part of the base class, they are available for every class you create. The wait() method provides the behavior you need for the pop() problem just discussed. When it is called, the wait() method releases the Object's monitor and simply waits until it is notified. Notification occurs when another thread calls the same Object's notify() method. This causes the waiting thread to wake up and reacquire the monitor. If the thread finds what it needs, it can leave the method and release the lock; otherwise, it can call wait() again.

Listing 8.7 shows modified methods of the TestStack class that implement the blocking behavior just discussed. The pop() method no longer throws an exception if the stack is empty. It now waits until the push() method calls notify() to indicate that an item has been added to the stack. In this example, the wait() method will wait indefinitely for an item to be added to the stack. In the Queue class developed in this chapter's project, you will see the use of alternate wait() methods that have time-outs associated with them.


Listing 8.7. The TestStack class with a blocking pop() method.
// Push an item onto the stack...
   public synchronized void push(Integer item) throws StackFullException {
      // Throw exception if stack is full...
      if (top == s.length)
            throw new StackFullException();
      // Otherwise increment the top and add the item...
      ++top;
      s[top] = item;
      notify();   // Let pop know you got something...
   }

   // Pop the top item off the stack...
   public synchronized Integer pop() {
      // Wait indefinitely if stack is empty...
      while (top < 0) {
         try {
            wait();
         }
         catch (InterruptedException e) { }
      } // end while
      // Otherwise, return the top item and decrement the top...
      Integer I = s[top];
      s[top] = null;
      -top;
      return I;
   }

Listing 8.8 shows the StackThread class that is modified to use the blocking pop() method. The run() method is a lot simpler than before because it no longer has to sleep between invocations of pop(). This is done implicitly when it is waiting for the TestStack to change.


Listing 8.8. The StackThread class modified to use the blocking pop() method.
// Thread that constantly reads the stack and prints out
// the current top...
class StackThread extends Thread {
   TestStack s;
   public StackThread(TestStack s) {
      this.s = s;
   }
   // Loop forever, looking at the top of the stack...
   public void run() {
      Integer Top;
      while (true) {
         // Print out top of stack, if stack isn't empty...
         // Wait indefinitely if the stack is empty...
         Top = s.pop();
         System.out.println("Thread: Read " + Top);
      }
   }
}

If more than one thread is waiting on an object, the notifyAll() method can be used to tell all waiting threads of the change in the object's state. On the other hand, the notify() method will send a signal only to a single waiting thread. It should also be noted that the notify() and wait() methods can be called only from within a synchronized method and from the thread that currently owns the object's monitor. For example, if the StackApplet object tried to call the TestStack object's notify() method, an IllegalMonitorStateException would be thrown.

More About Threads

Suppose you need one of your objects to sleep for a while, but the object is not an instance of the Thread class. How can this be done? The Thread class has a static method, called currentThread(), that returns a reference to the Thread object currently being executed. If the code is structured as in the following example, you can get the Thread reference you need:

public class MyClass {   // Note it does not extend Thread
   public MyMethod() {
      // ...work for a while...
      // Now sleep for 2 seconds
      Thread.currentThread().sleep(2000);
      // ...back to work...
   }
}

With the reference returned from currentThread(), you can execute the Thread methods that your class needs to perform. If you engage in serious Java programming, you will find the method to be useful in a wide variety of situations.

Another feature of the Thread class is that threads can run at different priority levels. Recall that threads generally operate by using time-slicing. Setting the priority of a thread lets you establish how large a time-slice your thread will get in relation to other threads. For example, a high-priority thread will get larger slices of time to process than a low-priority thread will. The Thread class supplies three public variables that can be used to define a thread's priority:

In general, these priorities are set at the values of 1, 5, and 10, respectively. However, it is best not to code with this knowledge because these variable definitions exist to hide their underlying values.

A thread's priority can be set with the setPriority() method. This method takes an integer priority value as its sole parameter. It throws an IllegalArgumentException if the priority does not fall within the bounds of MIN_PRIORITY and MAX_PRIORITY, inclusively. For example, this code sets the priority of a thread to the minimum value:

// Create the thread...
myThread t;
t = new MyThread().
// Set the priority to minimum
try {
  t.setPriority(Thread.MIN_PRIORITY);
}
// This exception will not occur!
catch (IllegalArgumentException e) {
  System.out.println(e.getMessage());
}
// Start the thread at the low priority...
t.start();

The priority of a thread can be retrieved with the getPriority() method. Table 8.1 provides a summary of Thread class methods that you may find useful. Note that ThreadGroups and daemons will be discussed next.

Table 8.1. Summary of important methods of the Thread class.

Method Description
currentThread() Returns a reference to the currently executing Thread.
getName() Gets the name of the Thread. Assigned manually or automatically in the constructor.
getPriority() Returns the priority of a Thread.
GetThreadGroup() Returns the ThreadGroup of a thread.
isAlive() Returns whether the Thread is alive; that is, it has been started but not stopped.
join() Waits for a Thread to die.
resume() Resumes the running of a suspended Thread.
run() Establishes where the threaded activity occurs.
setDaemon() Establishes the Thread as a daemon or user thread.
setPriority() Sets the priority of a Thread.
sleep() Causes a Thread to sleep for a specified amount of time.
stop() Terminates the run() method of a Thread.
suspend() Suspends the execution of a Thread.
yield() Yields the currently scheduled time slice to another Thread.

ThreadGroups

The ThreadGroup class contains Threads and other ThreadGroups. This hierarchical structure allows a ThreadGroup method, such as stop(), to be applied recursively over the Threads and ThreadGroups it contains. In this case, the stop() method terminates all currently executing threads in the ThreadGroup hierarchy. This recursive nature of ThreadGroups also makes it easy to coordinate the activities of running threads in the group with a single method call. For example, a call to a ThreadGroup's suspend() method will recursively suspend all threads in the group; its resume() method will just as easily resume the execution of the group's threads.

The Java runtime environment organizes all Threads according to a hierarchical structure of ThreadGroups. Every Thread belongs to a ThreadGroup. In an applet, the Thread of the Applet class belongs to the top-level ThreadGroup. Other Threads can be placed into that top-level group or placed into a new ThreadGroup.

The ThreadGroup class has two constructors, both of which require a string name. The second constructor allows you to specify the ThreadGroup in which the group should belong. If the default is used, the new ThreadGroup will be placed in the ThreadGroup of the currently executing thread. The following code creates an owner ThreadGroup that belongs in the currently executing thread's ThreadGroup and an owned group that belongs in the owner group:

// This goes into the currently executing thread's ThreadGroup
ThreadGroup ownerGrp = new ThreadGroup("Owner Group"); // The naem
// Create the child group...
ThreadGroup ownedGrp = new ThreadGroup(ownerGrp,"Owned Group");

To add a Thread to a ThreadGroup, you need to create it as part of the specified ThreadGroup. If the default Thread constructor is used, the Thread is placed into the ThreadGroup of the currently executing thread. The following code creates a Thread that belongs to the owner ThreadGroup of the previous example:

Thread t = new Thread(ownerGrp, "My Thread");

Note that names are important to ThreadGroups. You can get the name of a Thread or ThreadGroup through the corresponding getName() method. A Thread's name can be set at any time with setName(). A thread can find out which ThreadGroup it belongs to by calling the Thread method getThreadGroup(). For example, the following code can be used at any time to see what group a non-thread object is running in:

ThreadGroup grp = Thread.currentThread().getThreadGroup();

The various enumerate() methods of the Thread and ThreadGroup classes can be used to list the active threads or groups in an instance of the class. For example, the following code lists active threads in the owner ThreadGroup of the previous examples:

Thread outArray[] = new Thread[ownerGrp.activeCount()];
int count = ownerGrp.enumerate(outArray);
for (int i = 0; i < count; ++i)
  System.out.println(outArray[i]);

The size of the output array is determined by the activeCount() method of the ThreadGroup class. This returns the number of active Threads in the group. Note that inactive threads will not appear in these activeCount() or enumerate() methods. This might be a problem in some situations, so you may need to keep a manual list of threads. The ThreadGroup method activeGroupCount() returns the number of active ThreadGroups in the group. It corresponds to an enumerate() method similar to the one shown earlier.

One of the primary roles ThreadGroups plays is related to Java security. In theory, the access of a Thread or ThreadGroup to another ThreadGroup is restricted according to their relationship to each other. For example, a ThreadGroup can change its child ThreadGroups, but not the other way around. The checkAccess() method of the Thread and ThreadGroup classes is used to see whether access to a ThreadGroup is allowed; if not, then a SecurityException object is thrown. Unfortunately, the security feature of ThreadGroups does not seem to have been fully implemented in the JDK at the time of this writing. However, the methods and structure are in place, so it may be worthwhile to test these mechanisms to see whether your version of the JDK supports the ThreadGroup security function.

Another role of ThreadGroups is to control the priority of the threads running in it. The setMaxPriority() method is used to set the maximum priority of all the threads (recursively) in a ThreadGroup. This method will not affect currently running threads until their priorities are changed manually; however, the new maximum will apply to any threads started after this call. For example, if the priority is set to a value below NORMAL_PRIORITY (the default priority), all new threads will have their priorities set to this new lower value. Any time a currently executing thread changes its priority to a value higher than that established by setMaxPriority(), the priority applied will be that set by the ThreadGroup.

A Thread or ThreadGroup can be set as a daemon thread or group, respectively, by using the corresponding setDaemon() method. A daemon differs from the default state (a user thread) by being independent of the group that created the daemon; it is typically used to run in the background as some kind of service provider, such as a file loader or a print spooler (when printing is supported in AWT). A daemon thread will run until it is manually terminated or until all user threads have stopped running. A daemon ThreadGroup will be destroyed when it no longer has any active threads or ThreadGroup objects. The isDaemon() method can be used to check whether a thread or group is a daemon or user.

Now that you have seen ThreadGroups and the Runnable interface, you are now ready to look at the list of the various constructors for the Thread class, as shown in Table 8.2. They use various combinations of ThreadGroups, Runnable targets, and string names. If a ThreadGroup is not specified, the Thread is assigned to the ThreadGroup of the currently executing thread. If a string name is not provided, one is automatically assigned.

Table 8.2. Thread constructors.

ConstructorDescription
Thread() Default constructor.
Thread(Runnable) Constructs a thread with the provided Runnable object as the target of the run() method.
Thread(ThreadGroup, Runnable) Same as the previous, except it is assigned to the specified ThreadGroup.
Thread(String) Constructs a Thread with the specified name.
Thread(ThreadGroup, String) Constructs a Thread with the specified name, assigned to the specified ThreadGroup.
Thread(Runnable, String) Constructs a Thread with the specified name and Runnable target.
Thread(ThreadGroup, Runnable, String) Constructs a Thread with the specified name and Runnable target, assigned to the specified ThreadGroup.

ThreadDeath

One of the interesting things about threads is how the run() method is terminated. From an external user standpoint, a thread is stopped by calling the stop() method. Internal to the Thread class, however, something more interesting is going on. The stop() method results in an instance of an Error subclass, called ThreadDeath, being thrown at the victim thread. Note that ThreadDeath is not an Exception because these are usually caught as part of good programming practice. However, because errors are generally not caught, the thrown ThreadDeath object will result in the termination of the run() method.

You can catch ThreadDeath as part of your run() method if some extraordinary cleanup needs to occur when the thread is terminated. However, if you do this, you will need to rethrow the ThreadDeath object to ensure that the thread is actually terminated. Although this practice of catching ThreadDeath is discouraged for its potential risk, an example is shown here to illustrate how it works:

public void run() {
 while (true) {
  try {
   // Do normal threaded loop processing here...
  }
  catch (Exception e) {
    // Normal exceptions are caught here...
  }
  // Catch thread death!
  catch (ThreadDeath e) {
     // Clean up here...
     // Then rethrow the object to guarantee thread termination
     throw e;
  }
 }
}

An alternative stop() method can be used to throw your own customized thread "death." The syntax of the method is as follows:

public final synchronized stop(Throwable o)

The parameter can be any Throwable object, including Exceptions. Suppose you create your own Exception subclass called MyThreadDeath. You can then stop your custom thread by calling the following:

t.stop(new MyThreadDeath());

Your run() method should then catch the Exception:

catch(MyThreadDeath e) {
  // Do some special cleanup here...
  // Then throw ThreadDeath to really kill the thread...
  throw new ThreadDeath();
}

Make sure to throw ThreadDeath to kill the thread or to set a flag to exit the run() method gracefully.

Given these two stop() methods, it is easy to see that the standard stop() does little more than call the other version of stop(), with ThreadDeath as the Throwable object.

Talking Threads: Pipes and Threads

One of the interesting problems of multithreaded programming is how to get two threads to exchange information. A common method is to specify a shared communication object. In this chapter's project, the main thread and the MediaLoader thread talk by using a shared queue object. When the main thread needs an image to be loaded, it places the request into the queue. When the loader thread has retrieved the image, it places the image reference into a shared cache object.

Another way to exchange information between two threads is to create a series of methods for sharing data. One thread can "put" data while another does a "get" on the data. This technique is problematic, however, because the calling thread needs to know when the "get" is complete. By writing a shared object such as a queue or an instance of the TestStack class discussed earlier, you can solve the problem through a blocking mechanism that implements the wait() and notify() methods.

Another classical technique of interthread communication is the use of pipes. A pipe is a First-In First-Out (FIFO) mechanism best used by multiple threads. One thread exclusively writes to the pipe, but another exclusively reads from it. Blocking is easily implemented in pipes because the output (reader) thread can simply wait until an item is written to the pipe by the input thread.

This pipe model can be extended to work with the consumer-producer model. A consumer thread requests a service by writing to a pipe that the other thread constantly reads to see if it needs to produce something. Another pipe, with its input-output reversed, can be used to let the producer thread notify the consumer thread that the service request has been fulfilled. Figure 8.3 illustrates how this would work in a duplex pipe setup.

Figure 8.3 : Using pipes to implement a consumer-producer model

The java.io package provides an easy-to-use mechanism for implementing pipes. An instance of the PipedOutputStream is used to write data to a pipe with the write() method. A PipedInputStream instance is connected to the same pipe by using the read() method. An example, shown in Figure 8.4, uses pipes to take a string specified in a TextField object and reverse its contents. The applet, appearing in Listing 8.9, creates two pipes and two threads. Whenever you enter a string into a TextField and press Enter, it sends the string to an output pipe. Another class, the ReverseThread, is a threaded object that waits on the applet's output pipe-which is an input to it-and reads in a string; the ReverseThread class is found in Listing 8.10. The thread reverses the string and sends it to its own output pipe. The applet has its own thread, which waits for input from the ReverseThread class. When the applet thread gets the reversed string, it sets it to the display Label at the bottom of the string. Figure 8.5 shows the data flow of this pipe example; note how it is similar to Figure 8.3.

Figure 8.4 : An applet that uses pipes to reverse a string

Figure 8.5 : The data flow of the pipes's example


Listing 8.9. The applet code of the pipe program.
import java.awt.*;
import java.lang.*;
import java.applet.Applet;
import java.io.*;

// Applet that uses simple string reversal program
// to illustrate pipes...
public class PipeApplet extends Applet implements Runnable {
   TextField tf;  // You input the String to reverse here...
   Label reversed;  // Shows the reversed String...
   DataInputStream inPipe; // String request pipe...
   DataOutputStream outPipe; // String format...
   Thread t; // This thread
   ReverseThread r; // Thread to reverse Strings...

   // Create TextField to read text to reverse
   // and label to reverse it.
   public void init() {
      // Set up the components...
      setLayout(new BorderLayout());
      add("North",(tf = new TextField()));
      add("South",(reversed = new Label("Reversed String: ")) );
   }

   // Catch a text entry and write to a pipe...
   public boolean action(Event e,Object o) {
     // Take text field entry and stick in output pipe...
     if (e.target instanceof TextField) {
         try {
           outPipe.writeBytes(tf.getText()+"\n");
         }
         catch (IOException eio) {
           System.out.println("Applet Pipe Write Error: " + eio.getMessage());
         }
         tf.setText("");
     }
     return true;
   }

   // Main thread. Look for output from String Reverse pipe
   // and write to label...
   public void run() {
      String s;
      while (true) {
            try {
             // Print out reversed string to label...
             s = inPipe.readLine();
             reversed.setText("Reversed String: " + s);
            }
            catch (IOException eio) {
               System.out.println("Applet: Read error " + eio.getMessage());
            }
      }
   }

   // Entering Applet. Start the threads...
   // Create communication pipes between the threads...
   public void start() {
     if (t == null) {
      // Create the pipes...
      PipedOutputStream out;
      outPipe = new DataOutputStream(
          (out = new PipedOutputStream()));
      PipedInputStream in;
      inPipe = new DataInputStream(
          (in = new PipedInputStream()));
      // Create the thread objects...
      t = new Thread(this);
      r = new ReverseThread(in,out);
      // Start the threads...
      t.start();
      r.start();
     } // end if
   }

   // Leaving the Applet. Stop the threads...
   public void stop() {
     if (t != null) {
      t.stop();
      r.stop();
      try {
         t.join();
         r.join();
      }
      catch (InterruptedException e) { }
     } // end if
     t = null;
   }
}


Listing 8.10. The thread code of the pipe program.
// Thread that looks into pipe for strings to reverse...
class ReverseThread extends Thread {
   DataInputStream inPipe;  // Request stream...
   DataOutputStream outPipe;  // Send out reversed string here...
   // Construct with the pipes to use...
   public ReverseThread(PipedInputStream in, PipedOutputStream out) {
       try {
        inPipe = new DataInputStream(
            new PipedInputStream(out));
        outPipe = new DataOutputStream(
            new PipedOutputStream(in));
       }
       catch (IOException eio) {
         System.out.println("Thread: " + eio.getMessage());
       }
   }

   // Loop looking for threads to reverse...
   public void run() {
      String s;  // Input String
      StringBuffer rev; // The reverse string...
      while (true) {
            try {
             // Get a string to reverse...
             s = inPipe.readLine();
             // Create the reversal of the string
             rev = new StringBuffer();
             for (int i = (s.length() - 1); i >= 0; -i)
                     rev.append(s.charAt(i));
             // Send the reversed string to the caller!
             outPipe.writeBytes(rev + "\n");
            }
            catch (IOException eio) {
               System.out.println("Thread: Read error " + eio.getMessage());
            }
      }
   }
}

Chapter Project

Part III of this book concludes by adding a background media loader to the catalog applet developed in Chapter 6, "Building a Catalog Applet." This loader runs as a thread and retrieves images that are either to be used as soon as possible or are "preloaded" in anticipation of your going to another page in the catalog. This preloading feature improves the speed of your applet by loading images into memory before they are needed. Running the loader as a thread also improves performance because you can display a page of the catalog while the background thread is loading and preparing the images it needs.

Another thread that runs in the applet is a sweeper thread. This thread checks to see whether any items in the image cache have not been used for a while and so have aged beyond a useful time, therefore needing to be discarded. In the original version of this project in Chapter 6, the sweeper ran synchronously with the applet; when the sweeper was running, the applet would have to wait. In this version, the sweeper thread runs nonintrusively in the background. It is a low-priority thread, so its existence will probably go unnoticed.

The last enhancement to the catalog applet is linking in the BMP image producer discussed in the chapter project of Chapter 7, "Java and Images." The media loader thread checks to see the format of the image coming down; if it is a BMP file, it will use the BMP image producer. This makes the media loader act as a kind of "content handler," handling multiple kinds of content; at the very least, it is a first step toward having a real dynamic content handler as in HotJava.

The main menu of the catalog application is shown in Figure 8.6.

Figure 8.6 : Main menu of the online catalog

Class Organization

Table 8.3 lists the classes used in this chapter's version of the catalog applet. Because many of these classes were created in the previous chapters, the new classes are delimited by having their names appear in boldface type. The classes that were modified have their names italicized.

Table 8.3. Catalog project classes.

ClassDescription
BMPImage Used to produce Images based on the BMP format.
CacheEntry Represents a single entry in the image cache maintained by the MediaLoader class.
Catalog The Applet class that takes HTML parameters and constructs the components representing the current choices. Adds images to be preloaded from the MediaLoader.
CatalogButton An image-based button that shows text representing a choice and links to another Catalog applet when selected.
MediaLoader A class used to maintain a cache of images. It starts and sends requests to an instance of MediaLoaderThread to actually get the images and starts a MediaSweeper thread to remove any old images from the cache.
MediaLoaderException An exception thrown by the MediaLoader when there is a problem.
MediaLoaderThread A background thread that loads normal or BMP images as they are requested. It gets requests for images from a Queue object and adds loaded images to the image cache.
MediaObserver Used to track whether the loading of an image is complete.
MediaSweeper A background thread that removes any old images from the image cache.
Queue A generic class for reading or writing objects to a queue.
SelectionCanvas Displays a large image representing a possible choice of the user. Appears to the right of its companion CatalogButton.

Catalog HTML and Preload File

A new parameter is added to the catalog HTML file. The <PARAM> tag with the name preload specifies the file that contains images that should be loaded by the background MediaLoader thread after all of the images needed by the current catalog page have been loaded. Listing 8.11 shows the HTML of the main catalog page, with the preload parameter set in bold. Notice that the data associated with the parameter is now a bitmap file. Reading in this format is automatically handled by the BMPImage class.


Listing 8.11. HTML of main catalog page (index.html).
<title>Catalog Applet</title>
<hr>
<applet code="Catalog" width=400 height=300>
<param name=field1 value="Computers,catalog/field1.gif,MEDIUM,computer/main.html">
<param name=image1 value="catalog/selection1.gif">
<param name=field2 value="Software,catalog/field1.gif,MEDIUM,software/main.html">
<param name=image2 value="catalog/selection2.gif">
<param name=field3 value="Accessories,catalog/field1.gif,MEDIUM,accessory/
Âmain.html">
<param name=image3 value="catalog/selection3.bmp">
<param name=data value="catalog/data.txt">
<param name=preload value="preload">
</applet>
<hr>

Listing 8.12 displays the contents of the preload file that is tied to the HTML just listed. The entries of the preload file consist of a series of lines that list the following: the relative path and filename of the image to be preloaded, the style (for example, dimensions) of the file, and whether it goes on a CatalogButton object or a SelectionCanvas.


Listing 8.12. Preload file of the main catalog page.
computer/selection1.gif, MEDIUM, CANVAS
computer/field1.gif, MEDIUM, BUTTON
computer/selection3.gif, MEDIUM, CANVAS
software/softsel1.gif, MEDIUM, CANVAS
software/softsel3.gif, MEDIUM, CANVAS

The MediaLoaderThread and MediaObserver Classes

The MediaLoaderThread class, shown in Listing 8.13, is a background thread responsible for loading images that are requested in a shared instance of the Queue class. The heart of the class is its run() method. The thread waits indefinitely for input to appear in the input queue; as described later, the queue get() method is blocking, so the thread will do nothing until something is placed in the queue. When the appropriate data is found, the thread checks to see if the image is a BMP format or a standard image handled by Java. If it is the former, the BMPImage class is to produce an image based on the BMP format. If it is not a BMP file, then the Toolkit class is used to get an instance of the Image class. In either case, a MediaObserver object is created that is passed to the Toolkit's prepareImage() method. This forces the image to start being loaded. The MediaObserver will then track the progress of the image as it is being brought in. When the whole image is retrieved, the observer's complete flag is set. The MediaObserver class is shown in Listing 8.14. The ImageObserver interface on which it is based was described in the two previous chapters.


Listing 8.13. The MediaLoaderThread class.
// The MediaLoaderThread loads all images that appear in its
// input queue.  It works with the MediaObserver to track whether
// or not a standard image has been fully loaded.
public class MediaLoaderThread extends Thread {
   Queue inQueue;
   Hashtable cache;
   public static final int WAIT = 1;
   public static final int NO_WAIT = 2;
   public static final int LOAD_AND_WAIT = 3;
   static boolean debug = false;

   // Construct thread by giving it the cache hashtable
   // and the input queue
   public MediaLoaderThread(Hashtable cache,Queue q) {
       // Store the input queue...
       inQueue = q;
       this.cache = cache;
   }

   // Run the thread until it gets an orderly shutdown...
   public void run() {
      URL u;
      String s; // URL string from queue...
      int width,height,wait;
      boolean status;
      Image img;
      MediaObserver mo;
      ImageProducer producer;
      Integer I;
      while(true) {
         // See if there is something in the queue....
            s = (String) inQueue.get();
            debugString("Queue input: " + s);
            I = (Integer)inQueue.get();
            width = I.intValue();
            debugString("Queue input:  W = " + width);
            I = (Integer)inQueue.get();
            height = I.intValue();
            debugString("Queue input: H = " + height);
            I = (Integer)inQueue.get();
            wait = I.intValue();
            debugString("Queue input: Wait = " + wait);

         // Start loading the image....
         // Create a URL for the image...
         try {
              u = new URL(s);
              // See what type the image is.
              // BMPs are a special case...
              if (s.toUpperCase().endsWith(".BMP")) {
                  debugString("Thread: Load Bitmap...");
                  producer = BmpImage.getImageProducer(u);
                  img = Toolkit.getDefaultToolkit().createImage(producer);
              } // end if
              // Handle normal images (JPEG or GIF)
              else {
                 debugString("Thread: Load and prepare image...");
                 img = Toolkit.getDefaultToolkit().getImage(u);
                 img.flush();
              } // end else
              // Create MediaObserver and prepare image...
              mo = new MediaObserver(img,width,height);
              Toolkit.getDefaultToolkit().prepareImage(img,width,
                   height,mo);
              // If load and wait, stick in cache right away...
              if (wait == LOAD_AND_WAIT)
               cache.put(u,new CacheEntry(img,Integer.MAX_VALUE));
              // Loop until complete. BMPs won't go through here...
              if ((wait != NO_WAIT) && (mo != null)) {
               debugString("Thread: Start waiting...");
               while (true) {
                  if (mo.isComplete())
                        break;
                  // If not complete, sleep a little...
                  try {
                     sleep(100);
                  }
                  catch (InterruptedException e) { }
                }  // end while
              }  // end wait if
              debugString("Thread: Done loading");
              // Finished! Write to cache. High age prevents
              // premature garbage collection...
              if (wait != LOAD_AND_WAIT)
               cache.put(u,new CacheEntry(img,Integer.MAX_VALUE));
         }
         catch (MalformedURLException e) {
              System.out.println("Malformed URL");
         }
         catch (AWTException e) {
              System.out.println("AWTException: " + e);
         }
      } // end while
   }

   // See if we should print out debug strings...
   private void debugString(String s) {
      if (debug)
         System.out.println(s);
   }
}


Listing 8.14. The MediaObserver class.
// Observes an image as it's being loaded and tracks
// whether its loading is complete...
class MediaObserver implements ImageObserver {
   Image img;
   Boolean complete;
   static boolean debug = false;
   // Get image and dimensions...
   public MediaObserver(Image img,int width,int height) {
      this.img = img;
      complete = new Boolean(false);
   }

   // The complete flag needs to be synchronized...
   public boolean isComplete() {
      boolean status;
      synchronized(complete) {
            status = complete.booleanValue();
      }
      return status;
   }

   // Automatically called when an image is updated...
   public boolean imageUpdate(Image imgIn, int info,
      int x, int y, int width, int height) {
      if ((info & (ALLBITS | FRAMEBITS)) != 0) {
         synchronized(complete) {
            complete = new Boolean(true);
            if (debug)
               System.out.println("COMPLETE!!!");
         }
      }
      return true;
   }
}

The MediaLoaderThread class indicates that an image is prepared by adding it to the image Hashtable cache shared by the parent class (MediaLoader). When the image is added to the cache depends on the class's wait mode, of which it has three variations. If the mode is WAIT, the image is not entered into the cache until it has been fully loaded, indicated by the complete flag of the MediaObserver. (Recall that the getImage() method returns immediately and only creates an instance of the Image class; the image data is not loaded until a request is made for it by a method such as prepareImage().) If the mode is NO_WAIT, the image reference is placed immediately into the cache. For the LOAD_AND_WAIT mode, the image is placed immediately into the cache, but the next image request is not read until the image is fully loaded. This latter mode will be used for prefetching images. This mode of operation will work well in cases where the thread is in the middle of preloading an image that becomes immediately needed because you have moved to the page of the catalog in which the image appears.

Many enhancements can be made to these two classes. In particular, the MediaObserver can use the ImageObserver interface's ABORT flag to stop the loading of an image; the MediaLoaderThread class would also have to be adjusted. You might want to abort a load if you are moving to a new page of the catalog and you need its images loaded immediately. However, it is questionable whether this is actually worth while. Suppose that the image is 90 percent loaded, and you abort it. If the image is needed again shortly, network resources will have been wasted. Because this is a tough decision to make, the default procedure of letting the current load go through to completion was chosen.

The MediaLoader Class

The MediaLoader class was changed drastically from its original incarnation in Chapter 6. Consequently, its new design appears in Listing 8.15. The major change is that the actual loading of the images was moved into the MediaLoaderThread class, and the cleanup routines were placed into the MediaSweeper class. Both of these classes run as threads, which are created and started in the start() method. This is called once and only once by the private constructor. The start() method is also responsible for creating the Queue object that is used to place requests to the MediaLoaderThread object.


Listing 8.15. The MediaLoader class.
// This class loads image media via URL commands and
// keeps a local cache of images. Each image has an
// address that is used for aging. When the image is too
// old then it is removed from that cache.
public class MediaLoader {
   public static final int WAIT = MediaLoaderThread.WAIT;
   public static final int NO_WAIT =MediaLoaderThread.NO_WAIT;
   public static final int LOAD_AND_WAIT =MediaLoaderThread.LOAD_AND_WAIT;

   // Cache size is used for tracking age of URL
   static int cacheSize = 40;
   static int currentAge = 0;

   // Cache is hashtable...
   static Hashtable cache = new Hashtable(cacheSize);

   // The loader is static and so can be created only once
   private static MediaLoader loader = new MediaLoader();

   // Private internal constructor: Create the cache
   // abdstart the loaded thread...
   private MediaLoader() {
      System.out.println("Media Loader: CONSTRUCTOR!");
      start();
   }

   // Return reference to this MediaLoader object
   public static synchronized MediaLoader getMediaLoader() {
      return loader;
   }

   // Keeps track of whether or not it has started or
   // stopped the MediaLoaderThread object
   static boolean running = false;
   static MediaLoaderThread t;
   static MediaSweeper sweeper;
   static Queue reqQueue;


   // If not started yet, create output queue and
   // then start the media loader thread...
   public synchronized void start() {
      if (!running) {
         // Create request queue...
         reqQueue = new Queue(cacheSize * 4);
         // Start the media loader stream...
         t = new MediaLoaderThread(cache,reqQueue);
         t.start();
         // Start the sweeper. Try to keep cacheSize around
         // initial values...
         sweeper = new MediaSweeper(cache,cacheSize,cacheSize + 10);
         sweeper.start();
         running = true;
      }
   }

   // If running, shutdown the thread and wait for it to die...
   public synchronized void stop() {
      // Kill the thread if running...
      if (running) {
         // Orderly shutdown...
         t.stop();
         sweeper.stop();
         // Wait for thread to die...
         try {
            t.join();
            sweeper.join();
         }
         catch (InterruptedException e) { }
         // It's dead, Jim!
         running = false;
      }
   }

   // Get image via URL set for specified coordinates....
   // Will send this into the output queue that the media
   // thread reads...
   public synchronized Image getImage(URL base,String name,
    int width,int height,int wait) throws MediaLoaderException  {
      // Create a URL for the image...
      URL u;
      try {
           u = new URL(base,name);
      }
      catch (MalformedURLException e) {
           throw new MediaLoaderException("Malformed URL");
      }

      // See if it is in the cache...
      showStatus("Get image " + u + "...");
      ++currentAge;
      CacheEntry ce = (CacheEntry) cache.get(u);
      // If its in the cache, update the age and
      // return image...
      if (ce != null) {
         ce.setAge(currentAge);
         showStatus("Image found in cache...");
         return ce.getImage();
      }

      // Now write the URL and dimensions to the request array...
      reqQueue.put(u.toString());
      reqQueue.put(new Integer(width));
      reqQueue.put(new Integer(height));
      reqQueue.put(new Integer(wait));

      // The running thread determines the wait flag...
      // It either sets the Image object right away
      // or it waits for it...
      while (true) {
         ce = (CacheEntry) cache.get(u);
         if (ce != null)
               break;
         try {
            Thread.currentThread().sleep(100);
         }
         catch (InterruptedException e) { }
      }  // end while
      showStatus("Image " + name + " retrieved...");
      ce.setAge(currentAge);
      return ce.getImage();
   }


   // Applet is used for updating status bar...
   Applet a = null;

   // Called when a new applet has been loaded...
   public synchronized void enter(Applet a) {
      this.a = a;
   }

   // Called when the current applet is leaving...
   public synchronized void leave(Applet a) {
      this.a = null;
      // Clear the queue...
      reqQueue.reset();
   }

   // Show status of load...
   public void showStatus(String msg) {
      System.out.println("MediaLoader: " + msg);
      if (a != null)
         a.showStatus(msg);
   }

   // The identifying String of the loader
   public String toString() {
      return ("MediaLoader ID: " + hashCode());
   }
}

The getImage() method has the same parameters as before, except for one that indicates how to wait. The wait flags correspond to the discussion in the previous section. Internally, the getImage() method is greatly simplified. It checks to see if the image requested appears in the cache; if so, then it updates its age and returns it immediately. Otherwise, it places a request for the image to the loader thread via the shared Queue object. The MediaLoader does not release control until the image is placed into the cache; when this occurs implicitly depends on the wait flag.

The other thing worth noting in the MediaLoader class is the enter() and leave() methods. These are used for tracking the current applet; a reference to the applet allows the loader to update the browser's status bar with the current state of a request. When you leave a page of the catalog, the leave() method clears out the request queue of any pending image preloads. The loader thread will therefore finish only the current preload it is working on; it will then be free for any immediate requests needed for the new page.

The MediaSweeper Class

The MediaSweeper class is a low-priority thread that removes images from the MediaLoader cache whose age has grown beyond a certain acceptable value. This value is set by a desired cache size (the desiredSize variable). Whenever the size of the cache has grown bigger than a predetermined size (the cleanSize variable), the cache removes as many of the oldest cache entries as is necessary to get down to the desired size.

When the sweeper is constructed, it sets its priority to a low value by using the Thread class setPriority() method. When the sweeper thread is running, the run() method repeatedly checks to see whether the cache is bigger than the clean size, cleans if necessary, and then has a long sleep. If the cache is too big, the sweeper() method is called. This method, developed in Chapter 6, is only slightly changed to run inside the sweeper thread.

The contents of the MediaSweeper class appear in Listing 8.16.


Listing 8.16. The MediaSweeper class.
// Removes any item from cache that has an
// age that is too old...
   private void sweeper() {
      CacheEntry ce;
      // Array for placing hashtable elements...
      int ages[] = new int[cache.size()];
      // First step is to go through get all the ages...
      Enumeration em = cache.elements();
      for (int i = 0; em.hasMoreElements(); ++i) {
         ce = (CacheEntry)em.nextElement();
         ages[i] = ce.getAge();
      }
      // Next step is to get minimum age...
      // This is gross since we have to perform
      // a sort...
      sort(ages);
      // Now get nTh element
      int index = ages.length - desiredSize;
      int minAge = ages[index];
      int currentAge = ages[ages.length - 1];
      // Do nothing if we have nothing that's old...
      if (minAge > (currentAge - desiredSize)) {
         return;
      }

      // Final step is to walk through and remove
      // old elements...
      em = cache.keys();
      URL u;
      while (em.hasMoreElements()) {
         u = (URL)em.nextElement();
         // Get cache entry...
         ce = (CacheEntry)cache.get(u);
         // See if its too old...
         if (ce.getAge() < minAge) {
               cache.remove(u);
         }
      }
   }

   // Sort an integer array...
   // I didn't have enough time to write quick sort
   // so here is a lame bubble sort...
   void sort(int data[]) {
      int N = data.length - 1;
      int i,j,t;
      for (i = N; i >= 0; -i) {
         for (j = 1; j <= i; ++j) {
            if (data[j - 1] > data[j]) {
               t = data[j - 1];
               data[j - 1] = data[j];
               data[j] = t;
            } // end swap if
         } // end j if
      } // end i if
   }
}

The Queue Class

The Queue class implements a standard circular queue that uses objects as entries. The tail of the queue marks the index of the next entry in queue; the head is the last item read from the queue. Like a dog, the head chases the tail. If the head is equal to the tail, the queue is empty.

The most notable thing about the queue, shown in Listing 8.17, is that the put() and get() methods are blocking. If you try to put an element in a queue that is full, the method will wait until an element has been removed from the queue; it does this through the object wait() and notify() methods discussed earlier in this chapter. Similarly, the get() method blocks until there is an element in the queue to read. The put() and get() methods can be called with an indefinite time-out (value 0) or a millisecond timeout.


Listing 8.17. The Queue class.
// Generic class for holding a Queue of Objects...
// Uses synchronization and wait/notify methods to
// indicate when the Queue has something to read or
// is ready to accept a new entry...
public class Queue {
 Object Q[];
 int head,tail;
 // Create Queue of specified size....
 public Queue(int size) {
   Q = new Object[size];
   head = tail = 0;
 }

 // Add an element to the Queue.  If it is full then wait
 // until something has been read or it times out.
 // Timeout 0 means wait forever....
 public synchronized void put(Object o,long timeout)
  throws QueueTimeoutException {
   // Add the element to the queue...
   Q[tail] = o;
   int newTail = tail + 1;  // Increment tail...
   // Wrap around if the end is reached...
   if (newTail >= Q.length)
      newTail = 0;
   // See if the queue is full and so we have to wait...
   if (newTail == head) {
      System.out.println("Q Full. Wait...");
      try {
         wait(timeout);
      }
      catch (InterruptedException e) { }
      // Throw exception if we timed out...
      if (head == tail)
            throw new QueueTimeoutException();
   } // end if
   tail = newTail;
   notify();
 }

 // Default put: Wait forever...
 public void put(Object o) {
   try {
      put(o,0);
   }
   // This shouldn't happen...
   catch (QueueTimeoutException e) { }
 }

 // Get an element from the queue... Wait if queue
 // is empty. If empty, then wait until timeout,
 // where 0 means wait forever...
 public synchronized Object get(long timeout)
  throws QueueTimeoutException {
   // See if Queue is empty...
   if (head == tail) {
      System.out.println("Nothing in Q. Wait...");
      try {
         wait(timeout);
      }
      catch (InterruptedException e) { }
      // Throw exception if we timed out...
      if (head == tail)
            throw new QueueTimeoutException();
   } // end if
   // Get the Object at the head of the Queue...
   Object o = Q[head];
   // Reset the Q entry...
   Q[head] = null;
   // Set the new head...
   ++head;
   if (head >= Q.length)
         head = 0;
   notify();
   return o;
 }

 // Default put: Wait forever...
 public Object get() {
   try {
      return get(0);
   }
   // This shouldn't happen...
   catch (QueueTimeoutException e) {
      return null;
   }
 }

 // Reset the queue to be empty....
 public synchronized void reset() {
   head = tail = 0;
 }

 // List the Q elements....
 public synchronized void list() {
  for (int i = 0; i < Q.length; ++i)
      System.out.println(i + ": " + Q[i]);
 }
}

The BMPImage Class

The BMPImage class constructed in Chapter 7 needed two new methods to handle the requirements of the catalog applet. The extract4BitData() method is used to handle additional bitmap formats (see the CD-ROM for its code). Listing 8.18 includes an additional constructor for the BMPImage class. Unlike the other constructor, it takes only a full URL as its sole parameter.


Listing 8.18. An additional constructor for the BMPImage class.
/**
     * A method to retrieve an ImageProducer given just a BMP URL.
     * @param context contains the base URL (from getCodeBase() or such)
     * @returns an ImageProducer
     * @exception AWTException on stream or bitmap data errors
     */
    public static ImageProducer getImageProducer( URL context)
        throws AWTException
    {
        InputStream is = null;
        ImageProducer img = null;
        String name = context.toString();
        int index; // Make last part of URL  the name
        if ((index = name.lastIndexOf('/')) >= 0)
            name = name.substring(index + 1);
        try {
            BmpImage im = new BmpImage(name);
            is = context.openStream();
            DataInputStream input = new DataInputStream( new
                                          Buff eredInputStream(is) );
            img = im.extractImage(input);
        }
        catch (MalformedURLException me)
        {
            throw new AWTException(me.toString());
        }
        catch (IOException ioe)
        {
            throw new AWTException(ioe.toString());
        }
        return img;
    }

The Catalog Class

The start() method of the Catalog class is modified to notify the MediaLoader that a new catalog page is loaded, via the enter() method. It also starts the preload of images. This occurs in the preload() method. It reads in the file specified by the "preload" parameter and sends each entry as an image request to the MediaLoader. The LOAD_AND_WAIT flag tells the loader not to start loading any new requests until the current request is completely satisfied.

The stop() method tells the MediaLoader that you are leaving the current Catalog page by invoking its leave() method. This results in the clearing of the preload request queue. The changes to the Catalog class are shown in Listing 8.19.


Listing 8.19. Changes to the Catalog class.
// Begin preloading data...
   public void start() {
      // Let the loader know a new page has been loaded...
      MediaLoader.getMediaLoader().enter(this);
      // Start the preload of images...
      preload();
   }

   // Stop preload when leaving page...
   public void stop() {
      MediaLoader.getMediaLoader().leave(this);
   }

// Start preloading images if specified...
   public void preload() {
    // Get path to data from parameter...
    String dataPath = getParameter("preload");
    if (dataPath == null) {
      System.out.println("No preload variable found");
      return;
    } // end if
    // Create URL for data...
    URL u;
    try {
      u = new URL(getDocumentBase(),dataPath);
    }
    catch (MalformedURLException e) {
      System.out.println("Bad Data URL");
      return;
    }

    // Now load the data by opening up a stream
    // to the URL...
    try {
     DataInputStream dis = new DataInputStream(
      new BufferedInputStream(
         u.openStream() ) );
     // Read each line and put in MediaLoader queue...
     int i;
     String fetchLine, fileDir, tempStyle, tempType;
     while ((fetchLine = dis.readLine()) != null) {
         System.out.println("Prefetch: " + fetchLine);
         // Parse out the string...
         StringTokenizer s = new StringTokenizer(fetchLine,",");
         if (s.hasMoreTokens()) {
            fileDir = s.nextToken().trim();
            if (s.hasMoreTokens()) {
             tempStyle = s.nextToken().trim();
             // If we get past here, then all the fields are present
             // and we can start to preload the image...
             if (s.hasMoreTokens()) {
              tempType = s.nextToken().trim();
              // Right now only the Medium style is supported...
              try {
                 if (tempType.equalsIgnoreCase("button"))
                     MediaLoader.getMediaLoader().getImage(
                        getDocumentBase(),fileDir,150,100,
                        MediaLoader.LOAD_AND_WAIT);
                 else
                     MediaLoader.getMediaLoader().getImage(
                        getDocumentBase(),fileDir,250,100,
                        MediaLoader.LOAD_AND_WAIT);
              }
              // Just ignore errors....
              catch (MediaLoaderException e) {
                    System.out.println(e.getMessage());
              }
             } // end innermost if
            }
         }
     } // end while
    }
    catch (IOException e) {
      System.out.println("URL read error");
    }
   }

The CatalogButton Class

The constructor of the CatalogButton class was changed to use the new MediaLoader getImage() format, as shown in Listing 8.20. The wait flag is, oddly enough, NO_WAIT. This causes the MediaLoader to continue as soon as the Image object is created and not to wait until the image is fully loaded. This allows painting to occur as soon as possible, and the image to be rendered as it is loaded. It is worth seeing what happens when the flag of this class and SelectionCanvas is set to WAIT. Nothing will appear until everything is loaded. However, the images are shown in their full form, not partially rendered.


Listing 8.20. Changes to the CatalogButton constructor.
// Create the catalog button...
   public CatalogButton(Applet aIn,
     String backgroundImage,  String textIn, Font fIn,
      URL URLBaseIn, String linkIn, String dataIn,
      Font dataFontIn) {
         // Store parameters...
         a = aIn;
         text = textIn;
         f = fIn;
         URLBase = URLBaseIn;
         link = linkIn;
         lastX = lastY = -1;
         state = NORMAL_STATE;
         data = dataIn;
         dataFont = dataFontIn;
         // Now start loading the background image
         // via the Media Loader...
         try {
              img = MediaLoader.getMediaLoader().getImage(
               URLBase,backgroundImage,150,100,
               MediaLoader.NO_WAIT);
         }
         catch (MediaLoaderException e) {
            img = null;
            a.showStatus(e.getMessage());
         }
   }

The SelectionCanvas Class

The constructor of this class is modified in a manner similar to the CatalogButton class.
The wait flag of the getImage() call to the MediaLoader is also set to NO_WAIT, as shown in Listing 8.21.


Listing 8.21. Changes to the SelectionCanvas constructor.
// Create the canvas display...
   public SelectionCanvas(Applet aIn,
     String displayImage,URL URLBaseIn) {
         // Store parameters...
         a = aIn;
         URLBase = URLBaseIn;
         // Now start loading the background image
         // via the Media Loader...
         try {
            img = MediaLoader.getMediaLoader().getImage(
               URLBase,displayImage,250,100,
               MediaLoader.NO_WAIT);
         }
         catch (MediaLoaderException e) {
            img = null;
            a.getAppletContext().showStatus(e.getMessage());
         }
   }

Summary

This part of the book has emphasized how to use images for tasks like animation or smart image loading through a loader thread. The catalog applet developed in this chapter forms just the foundation for creating a serious online application. The media loader will need to be optimized to fit your particular needs, such as when to abort a load and how to use the wait flag to control how an image is displayed. But most of all, the catalog will probably need data from the server to show prices and other runtime information. This dynamic data loading is the subject of Part IV, "Managing Live Data"; in this part, you will develop an applet that dynamically reads in election night returns as they are fed in from a back-end server.