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

2.5 The Runnable Interface - Java Threads, Third Edition

Previous Section  < Day Day Up >  Next Section

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.

    Previous Section  < Day Day Up >  Next Section