站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Java Threads, Third Edition

2.4 Two Approaches to Stopping a Thread - Java Threads, Third Edition

Previous Section  < Day Day Up >  Next Section

2.4 Two Approaches to Stopping a Thread

When you want a thread to terminate based on some condition (e.g., the user has quit the game), you have several approaches available. Here we offer the two most common.

2.4.1 Setting a Flag

The most common way of stopping a thread is to set some internal flag to signal that the thread should stop. The thread can then periodically query that flag to determine if it should exit.

We can rewrite our RandomCharacterGenerator thread to follow this approach:

package javathreads.examples.ch02.example3;

...

public class RandomCharacterGenerator extends Thread implements CharacterSource {

    ...

    private volatile boolean done = false;

    ...

    public void run( ) {

        while (!done) {

            ...

               }

    }

    public void setDone( ) {

        done = true;

    }

}

Here we've created the boolean flag done to signal the thread that it should quit. Now instead of looping forever, the run() method examines the state of that variable on every loop and returns when the done flag has been set. That terminates the thread.[2]

[2] We've also introduced the use of the Java keyword volatile for that variable. Like the synchronized keyword, it is intrinsically related to thread programming (see Chapter 3).

We must now modify our application to set this flag:

package javathreads.examples.ch02.example3;

...

public class SwingTypeTester extends JFrame implements CharacterSource {

    ...

    private JButton stopButton;

    ...    

    private void initComponents( ) {

        ...

        stopButton = new JButton( );

        stopButton.setLabel("Stop");

        p.add(stopButton);

        ...

        stopButton.addActionListener(new ActionListener( ) {

            public void actionPerformed(ActionEvent evt) {

                startButton.setEnabled(true);

                stopButton.setEnabled(false);

                producer.setDone( );

                feedbackCanvas.setEnabled(false);

            }

        });

        ...

    }

    ...

}

Now we have two buttons: a Start and a Stop button. When the Stop button is pressed, the setDone() method is called, and the next time the RandomCharacterGenerator thread executes the top of its loop, that thread exits. This process also reenables the Start button: we can start a new thread at any time.

This raises an interesting design question: is it better to create a new thread like this, or would it be better somehow to suspend the existing thread and resume it when we're ready? Of course, we don't yet have the tools necessary to program the suspension and resumption of the thread, so that's the reason we've done it this way. It would be more natural simply to suspend and resume the thread, as we do in Chapter 4.

However, in a case like this, it actually does not matter. In our experience, developers become too hung up on the perceived performance penalties they attribute to creating a thread. If you're writing a program and it is easier to abandon a thread and create a new one rather than reusing an existing one, in most cases that's what you should do. We revisit this topic in more depth when we discuss thread pools in Chapter 10 and thread performance in Chapter 14.

Calling the setDone() method is a simple way for threads to communicate with each other. Threads must use special rules for communication like this (see Chapter 3). In general, though, threads can call methods on each other, as well as accessing the same objects, to pass information between themselves.

2.4.2 Interrupting a Thread

The last example has a delay between when the actionPerformed() method called the setDone() method and the RandomCharacterGenerator thread exited. Delays of some sort when arranging for a thread to terminate are inevitable, but sometimes the delay needs to be minimized.

In our example, the delay occurs because the RandomCharacterGenerator thread executes some number of statements after the setDone() method is called and before it checks the value of the done variable. In the worst case, the event thread executing the actionPerformed() method calls the setDone() method just after the RandomCharacterGenerator thread checks the value of the done variable. Then, even though it's done, the loop gets a new character out of the array, prints it to the screen, and goes to sleep for some amount of time. Finally it wakes up, returns to the top of the loop, sees that the done variable has been set to true, and returns.

The delay in this case is minimal, but it's likely to be close to the amount of time that the RandomCharacterGenerator thread is sleeping (since the other operations are very short). If we originally specify a 15-second delay, we probably won't want to wait the entire 15 seconds before the thread terminates.

In other cases, the delay can be worse: if the thread is executing a read() method to obtain data from a socket, the data may never come. Or the thread may be executing the wait() method (see Chapter 4) and waiting for an event that may never come. Methods like these are called blocking methods because they block execution of the thread until something happens (e.g., the expiration of the sleep() method).

When you arrange for a thread to terminate, you often want it to complete its blocking method immediately: you don't want to wait for the data (or whatever) anymore because the thread is going to exit anyway. You can use the interrupt() method of the Thread class to interrupt any blocking method.

The interrupt() method has two effects. First, it causes any blocked method to throw an InterruptedException. In our example, the sleep() method is a blocking method. If the event-processing thread interrupts the RandomCharacterGenerator thread while that thread is executing the sleep() method, the sleep method immediately wakes up and throws an InterruptedException. Other methods that behave this way include the wait() method, the join() method, and methods that read I/O (though there are complications when handling I/O, as we discuss Chapter 12).

The second effect is to set a flag inside the thread object that indicates the thread has been interrupted. To query this flag, use the isInterrupted() method. That method returns true if the thread has been interrupted (even if it was not blocked).

Here's how a thread uses this information to determine whether or not it should terminate:

package javathreads.examples.ch02.example4;



...

public class RandomCharacterGenerator extends Thread {

    ...

    // Note: the done instance variable and setDone( ) method are removed from

    // example 2



    public void run( ) {

        while (!isInterrupted( )) {

            ...

        }

    }

}

This example is almost exactly the same as the one in which we use a done flag to signal that the thread should return. In this case, we use the interrupted flag instead. That means we no longer need the setDone() method. Instead of calling the setDone() method, the actionPerformed( ) method associated with the Stop button in our application now does this:

producer.interrupt( );

If the main thread executes this statement while the RandomCharacterGenerator thread is sleeping, the RandomCharacterGenerator thread gets the interrupted exception and immediately returns from the run() method. Otherwise, when the character-feeding thread next gets to the top of its loop, it sees that the interrupted flag has been set and returns from its run() method then. Either way, the random character generator thread completes its task.

Note that this technique does not completely eliminate the possibility that we sleep for some amount of time after the thread is asked to stop. It's possible for the main thread to call the interrupt() method just after the RandomCharacterGenerator has called the isInterrupted() method. The character-reading thread still executes the sleep() method, which won't be interrupted (since the main thread has already completed the interrupt() method). This is another example of a race condition that we solve in the next chapter. Since the race condition in this case is benign (it just means we sleep one more time than we'd like), this is sufficient for our purposes.

    Previous Section  < Day Day Up >  Next Section