2.5 The Runnable Interface
When we talked about creating a thread,
we
mentioned the Runnable interface
(java.lang.Runnable). The
Thread class implements this interface, which
contains a single method:
package java.lang;
public interface Runnable {
public void run( );
}
The Runnable interface allows you to separate the
implementation of a task from the thread used to run the task. For
example, instead of extending the Thread class,
our RandomCharacterGenerator class might have
implemented the Runnable interface:
package javathreads.examples.ch02.example5;
...
// Note: Use Example 3 as the basis for comparison
public class RandomCharacterGenerator implements Runnable {
...
}
This changes the way in which the thread that runs the
RandomCharacterGenerator object must be
constructed:
package javathreads.examples.ch02.example5;
...
public class SwingTypeTester extends JFrame implements CharacterSource {
...
private void initComponents( ) {
...
startButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent evt) {
producer = new RandomCharacterGenerator( );
displayCanvas.setCharacterSource(producer);
Thread t = new Thread(producer);
t.start( );
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus( );
}
});
...
}
...
}
Now we must construct the thread directly and pass the runnable
object (producer) to the thread's
constructor. Then we start the thread (instead of starting the
runnable object).
This leads to the question of whether you should use the
Runnable interface or the
Thread class when designing your own application.
The answer is yes.
The truth is that sometimes it makes sense to use the
Runnable interface and sometimes it makes sense to
use the Thread class. The answer depends on
whether you would like your new class to inherit behavior from the
Thread class or if your class needs to inherit
from other classes.
If you extend the Thread class as we do in our
first examples, then you inherit the behavior and methods of the
Thread class. That is very important in example 4,
where we used the interrupt() method to signal
that the RandomCharacterGenerator should cease
operations. The interrupt() method is part of the
Thread class, and the reason why we are able to
interrupt the RandomCharacterGenerator thread is
because it extends the Thread class.
In fact, we should point out that the full source code for example 5
is based on example 3, not example 4. We have to use the
setDone() method to signal that the random
character generator's run()
method should terminate because that class no longer has an
interrupt() method. If we still want to interrupt
the sleep() method of the
RandomCharacterGenerator class, then we must write
the SwingTypeTester class like this:
package javathreads.examples.ch02.example6;
...
public class SwingTypeTester extends JFrame implements CharacterSource {
...
private void initComponents( ) {
...
stopButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent evt) {
...
displayThread.interrupt( );
}
});
...
}
A similar example can be used to show why it is sometimes preferable
to use the Runnable interface.
Let's suppose that we want the character in our
display canvas to move across the screen until the user types in the
matching character. This requires another thread, one that controls
the animation of the character. Every few milliseconds, the character
needs to be redisplayed on the canvas just slightly to the right of
where it was previously displayed. This makes the character appear to
be moving.
We could develop a brand new class to do this, but it shares most of
the logic of the existing CharacterDisplayCanvas
class. The newChar() method is somewhat different
and there's now some animation logic to deal with,
but clearly it's better in this example if we extend
CharacterDisplayCanvas (and inherit the methods
that set up the canvas size and font) than if we extend the
Thread class. This is a case that calls for the
Runnable interface:
package javathreads.examples.ch02.example7;
import java.awt.*;
import javax.swing.*;
import javathreads.examples.ch02.*;
public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
implements CharacterListener, Runnable {
private volatile boolean done = false;
private int curX = 0;
public AnimatedCharacterDisplayCanvas( ) {
}
public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
super(cs);
}
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint( );
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize( );
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0)
return;
int charWidth = fm.charWidth(tmpChar[0]);
gc.drawChars(tmpChar, 0, 1,
curX++, fontHeight);
}
public void run( ) {
while (!done) {
repaint( );
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
return;
}
}
}
public void setDone(boolean b) {
done = b;
}
}
This class demonstrates the canonical technique to handle animation
in Java: a thread makes successive calls to the repaint() method, which in turn calls the paintComponent(
) method. Every time the paintComponent() method is called, we display the character with a new X
coordinate that is slightly shifted to the right.
The thread that controls the animation in this canvas is created just
as before: the actionPerformed() method of the
Start button needs to create a new thread, passing in the
AnimatedCharacterCanvas as its runnable target. It
also needs to start that thread. The stop()
method, on the other hand, calls the setDone()
method to terminate the animation. Here's how it
looks:
package javathreads.examples.ch02.example7;
...
public class SwingTypeTester extends JFrame implements CharacterSource {
...
private void initComponents( ) {
...
startButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent evt) {
...
displayCanvas.setDone(false);
Thread t = new Thread(displayCanvas);
t.start( );
...
}
});
stopButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(true);
...
}
});
...
}
...
}
We began this section by wondering whether it was preferable to
program a task using the Runnable interface or the
Thread class. We've seen examples
of why you would need each. There's an additional
advantage to the Runnable interface, however. With
Runnable, Java provides a number of classes that
handle threading issues for you. These classes handle thread pooling,
task scheduling, or timing issues. If you're going
to use such a class, your task must be a Runnable
object (or, in some cases, an object that has an embedded
Runnable object).
If you do a thorough program design and Unified Modeling Language
(UML) model of your application, the resulting object hierarchy tells
you pretty clearly whether your task needs to subclass another class
(in which case you must use the Runnable
interface) or whether you need to use the methods of the
Thread class within your task. But if your object
hierarchy is silent on the parent class for your task, or if you do a
lot of prototyping or extreme programming, then what? Then the choice
is yours: you can use the Runnable interface,
which gives you a little more flexibility at the cost of the overhead
of keeping track of the thread objects separately, or you can trade that
flexibility for simplicity and subclass the Thread
class.
|