Runnable revisited
Runnable revisited
Earlier in this chapter, I suggested that you think
carefully before making an applet or main Frame as an implementation of
Runnable. 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
JPanel 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:
//:
c14:ColorBoxes.java
//
Using the Runnable interface.
//
<applet code=ColorBoxes width=500 height=400>
//
<param name=grid value="12">
//
<param name=pause value="50">
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
class
CBox
extends
JPanel
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
paintComponent(Graphics g) {
super.paintComponent(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) {
System.err.println("Interrupted");
}
}
}
}
public
class
ColorBoxes
extends
JApplet {
private
boolean
isApplet =
true;
private
int
grid = 12;
private
int
pause = 50;
public
void
init() {
// Get parameters from Web
page:
if
(isApplet) {
String gsize =
getParameter("grid");
if(gsize
!=
null)
grid = Integer.parseInt(gsize);
String pse =
getParameter("pause");
if(pse
!=
null)
pause = Integer.parseInt(pse);
}
Container cp = getContentPane();
cp.setLayout(new
GridLayout(grid, grid));
for
(int
i = 0; i < grid * grid; i++)
cp.add(new
CBox(pause));
}
public
static
void
main(String[] args) {
ColorBoxes applet =
new
ColorBoxes();
applet.isApplet =
false;
if(args.length
> 0)
applet.grid = Integer.parseInt(args[0]);
if(args.length
> 1)
applet.pause = Integer.parseInt(args[1]);
Console.run(applet, 500, 400);
}
}
///:~
ColorBoxes is the usual applet/application with
an init( ) that sets up the GUI. This sets 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,
or by using applet parameters.
CBox is where all the work takes place. This is
inherited from JPanel and it implements the Runnable interface so
each JPanel 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.
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.
paintComponent( ) is quite simple—it
just sets the color to cColor and fills the entire JPanel 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 JPanel 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.)
This program also makes an interesting benchmark,
since it can show dramatic performance differences between one JVM threading
implementation and another.
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 Swing
might have something to do with it, so here’s an example that tests that
premise by making fewer threads. The following code is reorganized so that an
ArrayList implements Runnable and that ArrayList holds a
number of color blocks and randomly chooses ones to update. Then a number of
these ArrayList 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:
//:
c14:ColorBoxes2.java
//
Balancing thread use.
//
<applet code=ColorBoxes2 width=600 height=500>
//
<param name=grid value="12">
//
<param name=pause value="50">
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
class
CBox2
extends
JPanel {
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
paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
}
class
CBoxList
extends
ArrayList
implements
Runnable {
private
Thread t;
private
int
pause;
public
CBoxList(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)get(i)).nextColor();
try
{
t.sleep(pause);
}
catch(InterruptedException
e) {
System.err.println("Interrupted");
}
}
}
public
Object last() {
return
get(size() - 1);}
}
public
class
ColorBoxes2
extends
JApplet {
private
boolean
isApplet =
true;
private
int
grid = 12;
// Shorter default pause than
ColorBoxes:
private
int
pause = 50;
private
CBoxList[] v;
public
void
init() {
// Get parameters from Web
page:
if
(isApplet) {
String gsize =
getParameter("grid");
if(gsize
!=
null)
grid = Integer.parseInt(gsize);
String pse =
getParameter("pause");
if(pse
!=
null)
pause = Integer.parseInt(pse);
}
Container cp = getContentPane();
cp.setLayout(new
GridLayout(grid, grid));
v =
new
CBoxList[grid];
for(int
i = 0; i < grid; i++)
v[i] =
new
CBoxList(pause);
for
(int
i = 0; i < grid * grid; i++) {
v[i %
grid].add(new
CBox2());
cp.add((CBox2)v[i % grid].last());
}
for(int
i = 0; i < grid; i++)
v[i].go();
}
public
static
void
main(String[] args) {
ColorBoxes2 applet =
new
ColorBoxes2();
applet.isApplet =
false;
if(args.length
> 0)
applet.grid = Integer.parseInt(args[0]);
if(args.length
> 1)
applet.pause = Integer.parseInt(args[1]);
Console.run(applet, 500, 400);
}
}
///:~
In ColorBoxes2 an array of CBoxList is
created and initialized to hold grid CBoxLists, each of which
knows how long to sleep. An equal number of CBox2 objects is then added
to each CBoxList, and each list 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 CBoxList.
The CBoxList could also have inherited
Thread and had a member object of type ArrayList. That design has
the advantage that the add( ) and get( ) 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 an
ArrayList. With all the casting and parentheses necessary for
get( ), 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 CBoxList
constructor and in go( ). The run( ) method simply
chooses a random element number within the list 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—here, the
slowdown in ColorBoxes appears to be caused by the fact that
there’s only one thread that is responsible for all painting, and it gets
bogged down by too many requests). If you have too many threads, 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?
Issues like this are one reason
that multithreaded programming is often considered an art
|