| Click here to support our advertisers

SHOPPING
JOB BANK
CLASSIFIEDS
DIRECTORIES
REFERENCE Online Library
LEARNING CENTER
JOURNAL
NEWS CENTRAL
DOWNLOADS
COMMUNITY
CALENDAR
ABOUT US
Journal:
Get the weekly email highlights from the most popular journal for developers! Current issue
> developer.com developerdirect.com htmlgoodies.com javagoodies.com jars.com intranetjournal.com javascripts.com | All Categories : Java # Chapter 8 # Adding Threads to Applets ### CONTENTS What Is a Thread? Creating a Thread with the Thread Class Enhancing Your First Multithreaded Applet The Runnable Interface Synchronization A TestStack Class That Is Not Thread-Safe Introducing the Synchronized Modifier Notify and Wait More About Threads ThreadGroups ThreadDeath Talking Threads: Pipes and Threads Chapter Project Class Organization Catalog HTML and Preload File The MediaLoaderThread and MediaObserver Classes The MediaLoader Class The MediaSweeper Class The Queue Class The BMPImage Class The Catalog Class The CatalogButton Class The SelectionCanvas Class Summary 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: `MIN_PRIORITY` indicates the minimum priority value in which a thread can run. A thread set to this priority will receive little processing time. It is best used for low-priority activities whose rate of completion isn't important. In this context, it is worth noting that the Java garbage collector runs at low priority (although it may not be `MIN_PRIORITY`). `NORMAL_PRIORITY` indicates the default value in which a thread runs. Most threads you create will run at this priority. `MAX_PRIORITY` indicates the maximum priority value in which a thread can run. A thread running at this priority will take most of the available CPU time. This should be used only for short CPU-intensive tasks that need to finish quickly. 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.
> Constructor | Description
> `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.
> Class | Description
> `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 BufferedInputStream(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. Contact reference@developer.com with questions or comments. Copyright 1998 EarthWeb Inc., All rights reserved. PLEASE READ THE ACCEPTABLE USAGE STATEMENT. Copyright 1998 Macmillan Computer Publishing. All rights reserved. |