Bruce Eckel’s Thinking in Java | Contents | Prev | Next |
in this chapter, I suggested that you think carefully before making an applet
or main
Frame
as an implementation of Runnable.
If you take that approach, you can make only one of those threads in your
program. This limits your flexibility if you decide that you want to have more
than one thread of that type.
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); } } ///:~
is a typical application with a constructor that sets up the GUI. This
constructor takes an argument of
int
grid
to set up the GridLayout
so that it has
grid
cells in each dimension. Then it adds the appropriate number of
CBox
objects to fill the grid, passing the
pause
value to each one. In
main( )
you can see how
pause
and
grid
have default values that can be changed if you pass in command-line arguments.
is
where all the work takes place. This is inherited from Canvas
and it implements the
Runnable
interface so each
Canvas
can also be a
Thread.
Remember that when you implement
Runnable,
you don’t make a
Thread
object, just a class that has a
run( )
method. Thus, you must explicitly create a
Thread
object and hand the
Runnable
object to the constructor, then call
start( )
(this happens in the constructor). In
CBox
this thread is called
t.
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.
is quite simple – it just sets the color to
cColor
and fills the entire canvas with that color.
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.
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.)
program also makes an interesting benchmark, since it can show dramatic speed
differences between one JVM implementation and another.
Too
many threads
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); } } ///:~
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.
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.
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.
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.
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:
- Do
you have enough calls to
sleep( ),
yield( ),
and/or
wait( )? - Are
calls to
sleep( )
long enough? - Are
you running too many threads? - Have
you tried different platforms and JVMs?