Runnable revisited

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Bruce Eckel’s Thinking in Java Contents | Prev | Next

Of
course, if you must inherit from a class
and
you want to add threading behavior to the class,
Runnable
is the correct solution. The final example in this chapter exploits this by
making a
Runnable
Canvas
class that paints different colors on itself. This application is set up to
take values from the command line to determine how big the grid of colors is
and how long to
sleep( )
between color changes. By playing with these values you’ll discover some
interesting and possibly inexplicable features of threads:

//: ColorBoxes.java
// Using the Runnable interface
import java.awt.*;
import java.awt.event.*;
 
class CBox extends Canvas implements Runnable {
  private Thread t;
  private int pause;
  private static final Color[] colors = {
    Color.black, Color.blue, Color.cyan,
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta,
    Color.orange, Color.pink, Color.red,
    Color.white, Color.yellow
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  public void paint(Graphics  g) {
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
  public CBox(int pause) {
    this.pause = pause;
    t = new Thread(this);
    t.start();
  }
  public void run() {
    while(true) {
      cColor = newColor();
      repaint();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {}
    }
  }
}
 
public class ColorBoxes extends Frame {
  public ColorBoxes(int pause, int grid) {
    setTitle("ColorBoxes");
    setLayout(new GridLayout(grid, grid));
    for (int i = 0; i < grid * grid; i++)
      add(new CBox(pause));
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }
  public static void main(String[] args) {
    int pause = 50;
    int grid = 8;
    if(args.length > 0)
      pause = Integer.parseInt(args[0]);
    if(args.length > 1)
      grid = Integer.parseInt(args[1]);
    Frame f = new ColorBoxes(pause, grid);
    f.setSize(500, 400);
    f.setVisible(true);
  }
} ///:~ 

Notice
the array
colors,
which is an enumeration of all the colors in class
Color.
This is used in
newColor( )
to produce a randomly-selected color. The current cell color is
cColor.

paint( )
is quite simple – it just sets the color to
cColor
and fills the entire canvas with that color.

In
run( ),
you see the infinite loop that sets the
cColor
to a new random color and then calls
repaint( )
to show it. Then the thread goes to
sleep( )
for the amount of time specified on the command line.

Precisely
because this design is flexible and threading is tied to each
Canvas
element, you can experiment by making as many threads as you want. (In reality,
there is a restriction imposed by the number of threads your JVM can
comfortably handle.)

Too
many threads

At
some point, you’ll find that
ColorBoxes
bogs down. On my machine, this occurred somewhere after a 10 x 10 grid. Why
does this happen? You’re naturally suspicious that the AWT might have
something to do with it, so here’s an example that tests that premise by
making fewer threads. The code is reorganized so that a
Vector
implements
Runnable

and that
Vector
holds a number of color blocks and randomly chooses ones to update. Then a
number of these
Vector
objects are created, depending roughly on the grid dimension you choose. As a
result, you have far fewer threads than color blocks, so if there’s a
speedup we’ll know it was because there were too many threads in the
previous example:

//: ColorBoxes2.java
// Balancing thread use
import java.awt.*;
import java.awt.event.*;
import java.util.*;
 
class CBox2 extends Canvas {
  private static final Color[] colors = {
    Color.black, Color.blue, Color.cyan,
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta,
    Color.orange, Color.pink, Color.red,
    Color.white, Color.yellow
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  void nextColor() {
    cColor = newColor();
    repaint();
  }
  public void paint(Graphics  g) {
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
}
 
class CBoxVector
  extends Vector implements Runnable {
  private Thread t;
  private int pause;
  public CBoxVector(int pause) {
    this.pause = pause;
    t = new Thread(this);
  }
  public void go() { t.start(); }
  public void run() {
    while(true) {
      int i = (int)(Math.random() * size());
      ((CBox2)elementAt(i)).nextColor();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {}
    }
  }
}
 
public class ColorBoxes2 extends Frame {
  private CBoxVector[] v;
  public ColorBoxes2(int pause, int grid) {
    setTitle("ColorBoxes2");
    setLayout(new GridLayout(grid, grid));
    v = new CBoxVector[grid];
    for(int i = 0; i < grid; i++)
      v[i] = new CBoxVector(pause);
    for (int i = 0; i < grid * grid; i++) {
      v[i % grid].addElement(new CBox2());
      add((CBox2)v[i % grid].lastElement());
    }
    for(int i = 0; i < grid; i++)
      v[i].go();
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
  }
  public static void main(String[] args) {
    // Shorter default pause than ColorBoxes:
    int pause = 5;
    int grid = 8;
    if(args.length > 0)
      pause = Integer.parseInt(args[0]);
    if(args.length > 1)
      grid = Integer.parseInt(args[1]);
    Frame f = new ColorBoxes2(pause, grid);
    f.setSize(500, 400);
    f.setVisible(true);
  }
} ///:~ 

In
ColorBoxes2
an array of
CBoxVector
is created and initialized to hold
grid
CBoxVectors,
each of which knows how long to sleep. An equal number of
Cbox2
objects is then added to each
CBoxVector,
and each vector is told to
go( ),
which starts its thread.

CBox2
is similar to
CBox:
it paints itself with a randomly-chosen color. But that’s
all
a
CBox2
does. All of the threading has been moved into
CBoxVector.

The
CBoxVector
could also have inherited
Thread
and had a member object of type
Vector.
That design has the advantage that the
addElement( )
and
elementAt( )
methods could then be given specific argument and return value types instead of
generic
Objects.
(Their names could also be changed to something shorter.) However, the design
used here seemed at first glance to require less code. In addition, it
automatically retains all the other behaviors of a
Vector.
With all the casting and parentheses necessary for
elementAt( ),
this might not be the case as your body of code grows.

As
before, when you implement
Runnable
you don’t get all of the equipment that comes with
Thread,
so you have to create a new
Thread
and hand yourself to its constructor in order to have something to
start( ),
as you can see in the
CBoxVector
constructor and in
go( ).
The
run( )
method simply chooses a random element number within the vector and calls
nextColor( )
for that element to cause it to choose a new randomly-selected color.

Upon
running this program, you see that it does indeed run faster and respond more
quickly (for instance, when you interrupt it, it stops more quickly), and it
doesn’t seem to bog down as much at higher grid sizes. Thus, a new factor
is added into the threading equation: you must watch to see that you
don’t have “too many threads” (whatever that turns out to
mean for your particular program and platform). If you do, you must try to use
techniques like the one above to “balance” the number of threads in
your program. If you see performance problems in a multithreaded program you
now have a number of issues to examine:

  1. Do
    you have enough calls to
    sleep( ),
    yield( ),
    and/or
    wait( )?
  2. Are
    calls to
    sleep( )
    long enough?
  3. Are
    you running too many threads?
  4. Have
    you tried different platforms and JVMs?
Issues
like this are one reason that multithreaded programming is often considered an
art.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read