Chapter 2. The Java ThreadingAPI

In this chapter, we will create our own threads. As we shall see, Java threads are easy to use and well integrated with the Java environment.

Threading Using the Thread Class

In the last chapter, we considered threads as separate tasks that execute in parallel. These tasks are simply code executed by the thread, and this code is actually part of our program. The code may download an image from the server or may play an audio file on the speakers or any other task; because it is code, it can be executed by our original thread. To introduce the parallelism we desire, we must create a new thread and arrange for the new thread to execute the appropriate code.

Let’s start by looking at the execution of a single thread in the following example:

public class OurClass {
    public void run() {
        for (int I = 0; I < 100; I++) {
            System.out.println("Hello");
        }
    }
}

In this example, we have a class called OurClass. The OurClass class has a single public method called run() that simply writes a string 100 times to the Java console or to the standard output. If we execute this code from an applet as shown here, it runs in the applet’s thread:

import java.applet.Applet;

public class OurApplet extends Applet {
    public void init() {
        OurClass oc = new OurClass();
        oc.run();
    }
}

If we instantiate an OurClass object and call its run() method, nothing unusual happens. An object is created, its run() method is called, and the “Hello” message prints 100 times. Just like other method calls, the caller of the run() method waits until the run() method finishes before it continues. If we were to graph an execution of the code, it would look like Figure 2.1.

Graphical representation of nonthreaded method execution

Figure 2-1. Graphical representation of nonthreaded method execution

What if we want the run() method of OurClass to execute in parallel with the init() and other methods of the applet? In order to do that, we must modify the OurClass class so that it can be executed by a new thread. So the first thing we’ll do is make OurClass inherit from the Thread (java.lang.Thread) class:

public class OurClass extends Thread {
    public void run() {
        for (int I = 0; I < 100; I++) {
            System.out.println("Hello");
        }
    }
}

If we compile this code and run it with our applet, everything works exactly as before: the applet’s init() method calls the run() method of the OurClass object and waits for the run() method to return before continuing. The fact that this example compiles and runs proves that the Thread class exists. This class is our first look into the Java threading API and is the programmatic interface for starting and stopping our own threads. But we have not yet created a new thread of control; we have simply created a class that has a run() method. To continue, let’s modify our applet like this:

import java.applet.Applet;

public class OurApplet extends Applet {
    public void init() {
        OurClass oc = new OurClass();
        oc.start();
    }
}

In this second version of our applet, we have changed only one line: the call to the run() method is now a call to the start() method. Compiling and executing this code confirms that it still works and appears to the user to run exactly the same way as the previous example. Since the start() method is not part of the OurClass class, we can conclude that the implementation of the start() method is part of either the Thread class or one of its superclasses. Furthermore, since the applet still accomplishes the same task, we can conclude that the start() method causes a call, whether directly or indirectly, to the run() method.

Upon closer examination, this new applet actually behaves differently than the previous version. While it is true that the start() method eventually calls the run() method, it does so in another thread. The start() method is what actually creates another thread of control; this new thread, after dealing with some initialization details, then calls the run() method. After the run() method completes, this new thread also deals with the details of terminating the thread. The start() method of the original thread returns immediately. Thus, the run() method will be executing in the newly formed thread at about the same time the start() method returns in the first thread, as shown in Figure 2.2.

Graphical representation of threaded method execution

Figure 2-2. Graphical representation of threaded method execution

Here are the methods of the Thread class that we’ve discussed so far:

Thread()

Constructs a thread object using default values for all options.

void run()

The method that the newly created thread will execute. Developers should override this method with the code they want the new thread to run; we’ll show the default implementation of the run() method a little further on, but it is essentially an empty method.

void start()

Creates a new thread and executes the run() method defined in this thread class.

To review, creating another thread of control is a two-step process. First, we must create the code that executes in the new thread by overriding the run() method in our subclass. Then we create the actual subclassed object using its constructor (which calls the default constructor of the Thread class in this case) and begin execution of its run() method by calling the start() method of the subclass.

Animate Applet

Let’s see a more concrete example of creating a new thread. When you want to show an animation in your web page, you do so by displaying a series of images (frames) with a time interval between the frames. This use of a timer is one of the most common places in Java where a separate thread is required: because there are no asynchronous signals in Java, you must set up a separate thread, have the thread sleep for a period of time, and then have the thread tell the applet to paint the next frame.

An implementation of this timer follows:

import java.awt.*;

public class TimerThread extends Thread {
    Component comp;             // Component that needs repainting
    int timediff;               // Time between repaints of the component
    volatile boolean shouldRun; // Set to false to stop thread

    public TimerThread(Component comp, int timediff) {
        this.comp = comp;
        this.timediff = timediff;
        shouldRun = true;
    }

    public void run() {
        while (shouldRun) {
            try {
                comp.repaint();
                sleep(timediff);
            } catch (Exception e) {}
        }
    }
}

In this example, the TimerThread class, just like the OurClass class, inherits from the Thread class and overrides the run() method. Its constructor stores the component on which to call the repaint() method and the requested time interval between the calls to the repaint() method.

What we have not seen so far is the call to the sleep() method:

static void sleep (long milliseconds)

Puts the currently executing thread to sleep for the specified number of milliseconds. This method is static and may be accessed through the Thread class name.

static void sleep (long milliseconds, int nanoseconds)

Puts the currently executing thread to sleep for the specified number of milliseconds and nanoseconds. This method is static and may be accessed through the Thread class name.

The sleep() method is part of the Thread class, and it causes the current thread (the thread that made the call to the sleep() method) to pause for the specified amount of time in milliseconds. The try statement in the code example is needed due to some of the exceptions that are thrown from the sleep() method. We’ll discuss these exceptions in Appendix B; for now, we’ll just discard all exceptions.

The easiest description of the task of the sleep() method is that the caller actually sleeps for the specified amount of time. This method is part of the Thread class because of how the method accomplishes the task: the current (i.e., calling) thread is placed in a “blocked” state for the specified amount of time, much like the state it would be in if the thread were waiting for I/O to occur. See Appendix A for a discussion of the volatile keyword.

To return to step 2 of the two-step process: let’s take a look at the Animate applet that uses our TimerThread class:

import java.applet.*;
import java.awt.*;

public class Animate extends Applet {
    int count, lastcount;
    Image pictures[];
    TimerThread timer;

    public void init() {
        lastcount = 10; count = 0;
        pictures = new Image[10];
        MediaTracker tracker = new MediaTracker(this);
        for (int a = 0; a < lastcount; a++) {
            pictures[a] = getImage (
                getCodeBase(), new Integer(a).toString()+".jpeg");
            tracker.addImage(pictures[a], 0);
        }
        tracker.checkAll(true);
    }

    public void start() {
        timer = new TimerThread(this, 1000);
        timer.start();
    }

    public void stop() {
        timer.shouldRun = false;
        timer = null;
    }

    public void paint(Graphics g) {
        g.drawImage(pictures[count++], 0, 0, null);

        if (count == lastcount)
            count = 0; 
    }
}

Here we create and start the new thread in the applet’s start() method. This new thread is responsible only for informing the applet when to redraw the next frame; it is still the applet’s thread that performs the redraw when the applet’s paint() method is called. The init() method in this case simply loads the image frames from the server.

Stopping a Thread

When the stop() method of the applet is called, we need to stop the timer thread, since we do not need repaint() requests when the applet is no longer running. To do this, we relied on the ability to set the shouldRun variable of the TimerThread class to notify that class that it should return from its run() method. When a thread returns from its run() method, it has completed its execution, so in this case we also set the timer instance variable to null to allow that thread object to be garbage collected.

This technique is the preferred method for terminating a thread: threads should always terminate by returning from their run() method. It’s up to the developer to decide how a thread should know when it’s time to return from the run() method; setting a flag, as we’ve done in this case, is typically the easiest method to do that.

Setting a flag means that my thread has to check the flag periodically. Isn’t there a cleaner way to stop the thread? And isn’t there a way to terminate the thread immediately, rather than waiting for it to check some flag? Well, yes and no. The Thread class does contain a stop() method that allows you to stop a thread immediately: no matter what the thread is doing, it will be terminated. However, the stop() method is very dangerous. In Java 2, the stop() method is deprecated; however, the reasons that led it to become deprecated actually exist in all versions of Java, so you should avoid using the stop() method in any release of Java. We’ll discuss the motivation for this in Chapter 6 after we understand a little more about the details of threaded programming; for now, you’ll have to accept our word that using the stop() method is a dangerous thing. In addition, calling the stop() method will sometimes result in a security exception, as we’ll explain in Chapter 10, so you cannot rely on it always working.

For the record, here is the definition of the stop() method:

void stop() (deprecated in Java 2)

Terminates an already running thread.

What does returning from the run() method (or calling the stop() method) accomplish? As we mentioned, when the run() method completes, the thread automatically handles the cleanup process and other details of terminating the thread. The stop() method simply provides a way of prematurely terminating the run() method. The thread will then, as usual, automatically handle the cleanup process and other details of terminating the thread. Details of how the stop() method actually works are given in Appendix A.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset