|
|||||
calc2 = new
JButton("Calculation 2");
static int
getValue(JTextField tf) {
try
{
return
Integer.parseInt(tf.getText());
}
catch(NumberFormatException e) {
return
0;
}
}
class
Calc1L implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t.setText(Integer.toString(
bl.calculation1(getValue(t))));
}
}
class
Calc2L implements ActionListener {
public
void actionPerformed(ActionEvent e) {
t.setText(Integer.toString(
bl.calculation2(getValue(t))));
}
}
//
If you want something to happen whenever
//
a JTextField changes, add this listener:
class
ModL implements DocumentListener {
public
void changedUpdate(DocumentEvent e) {}
public
void insertUpdate(DocumentEvent e) {
bl.setModifier(getValue(mod));
}
public
void removeUpdate(DocumentEvent e) {
bl.setModifier(getValue(mod));
}
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
calc1.addActionListener(new
Calc1L());
calc2.addActionListener(new
Calc2L());
JPanel
p1 = new JPanel();
p1.add(calc1);
p1.add(calc2);
cp.add(p1);
798
Thinking
in Java
mod.getDocument().
addDocumentListener(new
ModL());
JPanel
p2 = new JPanel();
p2.add(new
JLabel("Modifier:"));
p2.add(mod);
cp.add(p2);
}
public
static void main(String[] args) {
Console.run(new
Separation(), 250, 100);
}
}
///:~
You
can see that BusinessLogic
is
a straightforward class that
performs
its
operations without even a
hint that it might be used
in a GUI
environment.
It just does its
job.
Separation
keeps
track of all the UI details,
and it talks to
BusinessLogic
only
through its public
interface.
All the operations
are
centered
around getting information
back and forth through
the UI and
the
BusinessLogic
object.
So Separation,
in turn, just does its
job.
Since
Separation
knows
only that it's talking to a
BusinessLogic
object
(that is, it isn't highly
coupled), it could be massaged
into talking to
other
types of objects without
much trouble.
Thinking
in terms of separating UI from
business logic also makes
life
easier
when you're adapting legacy
code to work with
Java.
A
canonical form
Inner
classes, the Swing event
model, and the fact
that the old
event
model
is still supported along
with new library features
that rely on old-
style
programming has added a new
element of confusion to the
code
design
process. Now there are
even more different ways
for people to
write
unpleasant code.
Except
in extenuating circumstances you
can always use the
simplest and
clearest
approach: listener classes
(typically written as inner
classes) to
solve
your event-handling needs.
This is the form used in
most of the
examples
in this chapter.
Chapter
13: Creating Windows &
Applets
799
By
following this model you
should be able to reduce the
statements in
your
programs that say: "I wonder
what caused this event."
Each piece of
code
is concerned with doing,
not type-checking. This is
the best way to
write
your code; not only is it
easier to conceptualize, but
much easier to
read
and maintain.
Visual
programming
and
Beans
So
far in this book you've
seen how valuable Java is
for creating reusable
pieces
of code. The "most reusable"
unit of code has been
the class, since
it
comprises a cohesive unit of
characteristics (fields) and
behaviors
(methods)
that can be reused either
directly via composition or
through
inheritance.
Inheritance
and polymorphism are
essential parts of
object-oriented
programming,
but in the majority of cases
when you're putting
together
an
application, what you really
want is components that do
exactly what
you
need. You'd like to drop
these parts into your
design like the
electronic
engineer puts together chips
on a circuit board. It seems,
too,
that
there should be some way to
accelerate this "modular
assembly" style
of
programming.
"Visual
programming" first became
successful--very
successful--with
Microsoft's
Visual Basic (VB), followed
by a second-generation design in
Borland's
Delphi (the primary
inspiration for the
JavaBeans design).
With
these
programming tools the
components are represented
visually, which
makes
sense since they usually
display some kind of visual
component
such
as a button or a text field.
The visual representation, in
fact, is often
exactly
the way the component
will look in the running
program. So part
of
the process of visual
programming involves dragging a
component
from
a palette and dropping it
onto your form. The
application builder
tool
writes code as you do this,
and that code will
cause the component
to
be
created in the running
program.
Simply
dropping the component onto
a form is usually not enough
to
complete
the program. Often, you
must change the
characteristics of a
800
Thinking
in Java
component,
such as what color it is,
what text is on it, what
database it's
connected
to, etc. Characteristics
that can be modified at
design time are
referred
to as properties.
You can manipulate the
properties of your
component
inside the application
builder tool, and when
you create the
program
this configuration data is
saved so that it can be
rejuvenated
when
the program is
started.
By
now you're probably used to
the idea that an object is
more than
characteristics;
it's also a set of
behaviors. At design-time, the
behaviors
of
a visual component are
partially represented by events,
meaning
"Here's
something that can happen to
the component." Ordinarily,
you
decide
what you want to happen
when an event occurs by
tying code to
that
event.
Here's
the critical part: the
application builder tool
uses reflection to
dynamically
interrogate the component
and find out which
properties and
events
the component supports. Once
it knows what they are, it
can
display
the properties and allow
you to change those (saving
the state
when
you build the program),
and also display the
events. In general,
you
do
something like double-clicking on an
event and the application
builder
tool
creates a code body and
ties it to that particular
event. All you have
to
do
at that point is write the
code that executes when
the event occurs.
All
this adds up to a lot of
work that's done for
you by the
application
builder
tool. As a result you can
focus on what the program
looks like and
what
it is supposed to do, and
rely on the application
builder tool to
manage
the connection details for
you. The reason that
visual
programming
tools have been so
successful is that they
dramatically
speed
up the process of building an
application--certainly the
user
interface,
but often other portions of
the application as
well.
What
is a Bean?
After
the dust settles, then, a
component is really just a
block of code,
typically
embodied in a class. The key
issue is the ability for
the
application
builder tool to discover the
properties and events for
that
component.
To create a VB component, the
programmer had to write
a
fairly
complicated piece of code
following certain conventions to
expose
the
properties and events.
Delphi was a second-generation
visual
programming
tool and the language
was actively designed around
visual
Chapter
13: Creating Windows &
Applets
801
programming
so it is much easier to create a
visual component.
However,
Java
has brought the creation of
visual components to its
most advanced
state
with JavaBeans, because a
Bean is just a class. You
don't have to
write
any extra code or use
special language extensions in
order to make
something
a Bean. The only thing
you need to do, in fact, is
slightly
modify
the way that you
name your methods. It is the
method name that
tells
the application builder tool
whether this is a property, an
event, or
just
an ordinary method.
In
the Java documentation, this
naming convention is mistakenly
termed
a
"design pattern." This is
unfortunate, since design
patterns (see
Thinking
in Patterns with Java, downloadable
at )
are
challenging enough without
this sort of confusion. It's
not a design
pattern,
it's just a naming
convention and it's fairly
simple:
1.
For
a property named xxx,
you typically create two
methods:
getXxx(
) and
setXxx(
).
Note that the first
letter after "get" or
"set"
is automatically lowercased to produce
the property name.
The
type produced by the "get"
method is the same as the
type of
the
argument to the "set"
method. The name of the
property and
the
type for the "get"
and "set" are not
related.
2.
For
a boolean
property,
you can use the
"get" and "set"
approach
above,
but you can also
use "is" instead of
"get."
3.
Ordinary
methods of the Bean don't
conform to the above
naming
convention,
but they're public.
4.
For
events, you use the
Swing "listener" approach.
It's exactly the
same
as you've been
seeing:
addFooBarListener(FooBarListener)
and
removeFooBarListener(FooBarListener)
to
handle a
FooBarEvent.
Most of the time the
built-in events and
listeners
will
satisfy your needs, but
you can also create
your own events
and
listener
interfaces.
Point
1 above answers a question
about something you might
have noticed
when
looking at older code vs.
newer code: a number of
method names
have
had small, apparently
meaningless name changes.
Now you can
see
that
most of those changes had to
do with adapting to the
"get" and "set"
802
Thinking
in Java
naming
conventions in order to make
that particular component
into a
Bean.
We
can use these guidelines to
create a simple Bean:
//:
frogbean:Frog.java
//
A trivial JavaBean.
package
frogbean;
import
java.awt.*;
import
java.awt.event.*;
class
Spots {}
public
class Frog {
private
int jumps;
private
Color color;
private
Spots spots;
private
boolean jmpr;
public
int getJumps() { return jumps; }
public
void setJumps(int newJumps) {
jumps
= newJumps;
}
public
Color getColor() { return color; }
public
void setColor(Color newColor) {
color
= newColor;
}
public
Spots getSpots() { return spots; }
public
void setSpots(Spots newSpots) {
spots
= newSpots;
}
public
boolean isJumper() { return jmpr; }
public
void setJumper(boolean j) { jmpr = j; }
public
void addActionListener(
ActionListener
l) {
//...
}
public
void removeActionListener(
ActionListener
l) {
//
...
}
public
void addKeyListener(KeyListener l) {
Chapter
13: Creating Windows &
Applets
803
//
...
}
public
void removeKeyListener(KeyListener l) {
//
...
}
//
An "ordinary" public method:
public
void croak() {
System.out.println("Ribbet!");
}
}
///:~
First,
you can see that
it's just a class. Usually,
all your fields will
be
private,
and accessible only through
methods. Following the
naming
convention,
the properties are jumps,
color,
spots,
and jumper
(notice
the
case change of the first
letter in the property
name). Although the
name
of the internal identifier is
the same as the name of
the property in
the
first three cases, in
jumper
you
can see that the
property name does
not
force you to use any
particular identifier for
internal variables
(or,
indeed,
to even have
any
internal variables for that
property).
The
events this Bean handles
are ActionEvent
and
KeyEvent,
based on
the
naming of the "add" and
"remove" methods for the
associated listener.
Finally,
you can see that
the ordinary method
croak( )
is
still part of the
Bean
simply because it's a
public
method,
not because it conforms to
any
naming
scheme.
Extracting
BeanInfo
with
the Introspector
One
of the most critical parts
of the Bean scheme occurs
when you drag a
Bean
off a palette and plop it
onto a form. The application
builder tool
must
be able to create the Bean
(which it can do if there's a
default
constructor)
and then, without access to
the Bean's source code,
extract
all
the necessary information to
create the property sheet
and event
handlers.
Part
of the solution is already
evident from the end of
Chapter 12: Java
reflection
allows
all the methods of an
anonymous class to be
discovered.
This
is perfect for solving the
Bean problem without
requiring you to use
any
extra language keywords like
those required in other
visual
804
Thinking
in Java
programming
languages. In fact, one of
the prime reasons that
reflection
was
added to Java was to support
Beans (although reflection
also
supports
object serialization and
remote method invocation). So
you
might
expect that the creator of
the application builder tool
would have to
reflect
each Bean and hunt
through its methods to find
the properties and
events
for that Bean.
This
is certainly possible, but
the Java designers wanted to
provide a
standard
tool, not only to make
Beans simpler to use but
also to provide a
standard
gateway to the creation of
more complex Beans. This
tool is the
Introspector
class,
and the most important
method in this class is
the
static
getBeanInfo( ). You
pass a Class
reference
to this method and it
fully
interrogates that class and
returns a BeanInfo
object
that you can
then
dissect to find properties,
methods, and events.
Usually
you won't care about
any of this--you'll probably
get most of your
Beans
off the shelf from
vendors, and you don't
need to know all
the
magic
that's going on underneath.
You'll simply drag your
Beans onto
your
form, then configure their
properties and write
handlers for the
events
you're interested in.
However, it's an interesting
and educational
exercise
to use the Introspector
to
display information about a
Bean, so
here's
a tool that does
it:
//:
c13:BeanDumper.java
//
Introspecting a Bean.
//
<applet code=BeanDumper width=600
height=500>
//
</applet>
import
java.beans.*;
import
java.lang.reflect.*;
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class BeanDumper extends JApplet {
JTextField
query =
new
JTextField(20);
JTextArea
results = new JTextArea();
public
void prt(String s) {
results.append(s
+ "\n");
Chapter
13: Creating Windows &
Applets
805
}
public
void dump(Class bean){
results.setText("");
BeanInfo
bi = null;
try
{
bi
= Introspector.getBeanInfo(
bean,
java.lang.Object.class);
}
catch(IntrospectionException e) {
prt("Couldn't
introspect " +
bean.getName());
return;
}
PropertyDescriptor[]
properties =
bi.getPropertyDescriptors();
for(int
i = 0; i < properties.length; i++) {
Class
p = properties[i].getPropertyType();
prt("Property
type:\n " + p.getName() +
"Property
name:\n " +
properties[i].getName());
Method
readMethod =
properties[i].getReadMethod();
if(readMethod
!= null)
prt("Read
method:\n " + readMethod);
Method
writeMethod =
properties[i].getWriteMethod();
if(writeMethod
!= null)
prt("Write
method:\n " + writeMethod);
prt("====================");
}
prt("Public
methods:");
MethodDescriptor[]
methods =
bi.getMethodDescriptors();
for(int
i = 0; i < methods.length; i++)
prt(methods[i].getMethod().toString());
prt("======================");
prt("Event
support:");
EventSetDescriptor[]
events =
bi.getEventSetDescriptors();
for(int
i = 0; i < events.length; i++) {
prt("Listener
type:\n " +
events[i].getListenerType().getName());
806
Thinking
in Java
Method[]
lm =
events[i].getListenerMethods();
for(int
j = 0; j < lm.length; j++)
prt("Listener
method:\n " +
lm[j].getName());
MethodDescriptor[]
lmd =
events[i].getListenerMethodDescriptors();
for(int
j = 0; j < lmd.length; j++)
prt("Method
descriptor:\n " +
lmd[j].getMethod());
Method
addListener =
events[i].getAddListenerMethod();
prt("Add
Listener Method:\n " +
addListener);
Method
removeListener =
events[i].getRemoveListenerMethod();
prt("Remove
Listener Method:\n " +
removeListener);
prt("====================");
}
}
class
Dumper implements ActionListener {
public
void actionPerformed(ActionEvent e) {
String
name = query.getText();
Class
c = null;
try
{
c
= Class.forName(name);
}
catch(ClassNotFoundException ex) {
results.setText("Couldn't
find " + name);
return;
}
dump(c);
}
}
public
void init() {
Container
cp = getContentPane();
JPanel
p = new JPanel();
p.setLayout(new
FlowLayout());
p.add(new
JLabel("Qualified bean name:"));
p.add(query);
cp.add(BorderLayout.NORTH,
p);
Chapter
13: Creating Windows &
Applets
807
cp.add(new
JScrollPane(results));
Dumper
dmpr = new Dumper();
query.addActionListener(dmpr);
query.setText("frogbean.Frog");
//
Force evaluation
dmpr.actionPerformed(
new
ActionEvent(dmpr, 0, ""));
}
public
static void main(String[] args) {
Console.run(new
BeanDumper(), 600, 500);
}
}
///:~
BeanDumper.dump(
) is
the method that does
all the work. First
it
tries
to create a BeanInfo
object,
and if successful calls the
methods of
BeanInfo
that
produce information about
properties, methods,
and
events.
In Introspector.getBeanInfo(
),
you'll see there is a
second
argument.
This tells the Introspector
where
to stop in the
inheritance
hierarchy.
Here, it stops before it
parses all the methods
from Object,
since
we're not interested in
seeing those.
For
properties, getPropertyDescriptors(
) returns
an array of
PropertyDescriptors.
For each PropertyDescriptor
you
can call
getPropertyType(
) to
find the class of object
that is passed in and
out
via
the property methods. Then,
for each property you
can get its
pseudonym
(extracted from the method
names) with getName(
),
the
method
for reading with getReadMethod(
),
and the method for
writing
with
getWriteMethod(
).
These last two methods
return a Method
object
that can actually be used to
invoke the corresponding
method on
the
object (this is part of
reflection).
For
the public
methods
(including the property
methods),
getMethodDescriptors(
) returns
an array of MethodDescriptors.
For
each one you can
get the associated Method
object
and print its
name.
For
the events, getEventSetDescriptors(
) returns
an array of (what
else?)
EventSetDescriptors.
Each of these can be queried
to find out
the
class of the listener, the
methods of that listener
class, and the
add-
808
Thinking
in Java
and
remove-listener methods. The
BeanDumper
program
prints out all
of
this information.
Upon
startup, the program forces
the evaluation of frogbean.Frog.
The
output,
after removing extra details
that are unnecessary here,
is:
class
name: Frog
Property
type:
Color
Property
name:
color
Read
method:
public
Color getColor()
Write
method:
public
void setColor(Color)
====================
Property
type:
Spots
Property
name:
spots
Read
method:
public
Spots getSpots()
Write
method:
public
void setSpots(Spots)
====================
Property
type:
boolean
Property
name:
jumper
Read
method:
public
boolean isJumper()
Write
method:
public
void setJumper(boolean)
====================
Property
type:
int
Property
name:
jumps
Read
method:
public
int getJumps()
Write
method:
Chapter
13: Creating Windows &
Applets
809
public
void setJumps(int)
====================
Public
methods:
public
void setJumps(int)
public
void croak()
public
void removeActionListener(ActionListener)
public
void addActionListener(ActionListener)
public
int getJumps()
public
void setColor(Color)
public
void setSpots(Spots)
public
void setJumper(boolean)
public
boolean isJumper()
public
void addKeyListener(KeyListener)
public
Color getColor()
public
void removeKeyListener(KeyListener)
public
Spots getSpots()
======================
Event
support:
Listener
type:
KeyListener
Listener
method:
keyTyped
Listener
method:
keyPressed
Listener
method:
keyReleased
Method
descriptor:
public
void keyTyped(KeyEvent)
Method
descriptor:
public
void keyPressed(KeyEvent)
Method
descriptor:
public
void keyReleased(KeyEvent)
Add
Listener Method:
public
void addKeyListener(KeyListener)
Remove
Listener Method:
public
void removeKeyListener(KeyListener)
====================
Listener
type:
ActionListener
Listener
method:
actionPerformed
810
Thinking
in Java
Method
descriptor:
public
void actionPerformed(ActionEvent)
Add
Listener Method:
public
void addActionListener(ActionListener)
Remove
Listener Method:
public
void removeActionListener(ActionListener)
====================
This
reveals most of what the
Introspector
sees
as it produces a
BeanInfo
object
from your Bean. You
can see that the
type of the
property
and its name are
independent. Notice the
lowercasing of the
property
name. (The only time
this doesn't occur is when
the property
name
begins with more than
one capital letter in a
row.) And remember
that
the method names you're
seeing here (such as the
read and write
methods)
are actually produced from a
Method
object
that can be used
to
invoke the associated method
on the object.
The
public
method
list includes the methods
that are not associated
with
a
property or event, such as
croak(
),
as well as those that are.
These are
all
the methods that you
can call programmatically
for a Bean, and
the
application
builder tool can choose to
list all of these while
you're making
method
calls, to ease your
task.
Finally,
you can see that
the events are fully
parsed out into the
listener,
its
methods, and the add-
and remove-listener methods.
Basically, once
you
have the BeanInfo,
you can find out
everything of importance
for
the
Bean. You can also
call the methods for
that Bean, even though
you
don't
have any other information
except the object (again, a
feature of
reflection).
A
more sophisticated Bean
This
next example is slightly
more sophisticated, albeit
frivolous. It's a
JPanel
that
draws a little circle around
the mouse whenever the
mouse is
moved.
When you press the
mouse, the word "Bang!"
appears in the
middle
of the screen, and an action
listener is fired.
The
properties you can change
are the size of the
circle as well as the
color,
size, and text of the
word that is displayed when
you press the
mouse.
A BangBean
also
has its own addActionListener(
) and
Chapter
13: Creating Windows &
Applets
811
removeActionListener(
) so you
can attach your own
listener that will
be
fired when the user
clicks on the BangBean.
You should be able to
recognize
the property and event
support:
//:
bangbean:BangBean.java
//
A graphical Bean.
package
bangbean;
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.io.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
public
class BangBean extends JPanel
implements
Serializable {
protected
int xm, ym;
protected
int cSize = 20; // Circle size
protected
String text = "Bang!";
protected
int fontSize = 48;
protected
Color tColor = Color.red;
protected
ActionListener actionListener;
public
BangBean() {
addMouseListener(new
ML());
addMouseMotionListener(new
MML());
}
public
int getCircleSize() { return cSize; }
public
void setCircleSize(int newSize) {
cSize
= newSize;
}
public
String getBangText() { return text; }
public
void setBangText(String newText) {
text
= newText;
}
public
int getFontSize() { return fontSize; }
public
void setFontSize(int newSize) {
fontSize
= newSize;
}
public
Color getTextColor() { return tColor; }
public
void setTextColor(Color newColor) {
tColor
= newColor;
812
Thinking
in Java
}
public
void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.black);
g.drawOval(xm
- cSize/2, ym - cSize/2,
cSize,
cSize);
}
//
This is a unicast listener, which is
//
the simplest form of listener management:
public
void addActionListener (
ActionListener
l)
throws
TooManyListenersException {
if(actionListener
!= null)
throw
new TooManyListenersException();
actionListener
= l;
}
public
void removeActionListener(
ActionListener
l) {
actionListener
= null;
}
class
ML extends MouseAdapter {
public
void mousePressed(MouseEvent e) {
Graphics
g = getGraphics();
g.setColor(tColor);
g.setFont(
new
Font(
"TimesRoman",
Font.BOLD, fontSize));
int
width =
g.getFontMetrics().stringWidth(text);
g.drawString(text,
(getSize().width
- width) /2,
getSize().height/2);
g.dispose();
//
Call the listener's method:
if(actionListener
!= null)
actionListener.actionPerformed(
new
ActionEvent(BangBean.this,
ActionEvent.ACTION_PERFORMED,
null));
}
}
class
MML extends MouseMotionAdapter {
Chapter
13: Creating Windows &
Applets
813
public
void mouseMoved(MouseEvent e) {
xm
= e.getX();
ym
= e.getY();
repaint();
}
}
public
Dimension getPreferredSize() {
return
new Dimension(200, 200);
}
}
///:~
The
first thing you'll notice is
that BangBean
implements
the
Serializable
interface.
This means that the
application builder tool
can
"pickle"
all the information for
the BangBean
using
serialization after
the
program designer has
adjusted the values of the
properties. When the
Bean
is created as part of the
running application, these
"pickled"
properties
are restored so that you
get exactly what you
designed.
You
can see that all
the fields are private,
which is what you'll usually
do
with
a Bean--allow access only
through methods, usually
using the
"property"
scheme.
When
you look at the signature
for addActionListener(
),
you'll see
that
it can throw a TooManyListenersException.
This indicates that
it
is
unicast,
which means it notifies only
one listener when the
event
occurs.
Ordinarily, you'll use
multicast
events
so that many listeners
can
be
notified of an event. However,
that runs into issues
that you won't be
ready
for until the next
chapter, so it will be revisited
there (under the
heading
"JavaBeans revisited"). A unicast
event sidesteps the
problem.
When
you click the mouse,
the text is put in the
middle of the BangBean,
and
if the actionListener
field
is not null,
its actionPerformed(
) is
called,
creating a new ActionEvent
object
in the process. Whenever
the
mouse
is moved, its new
coordinates are captured and
the canvas is
repainted
(erasing any text that's on
the canvas, as you'll
see).
Here
is the BangBeanTest
class
to allow you to test the
bean as either an
applet
or an application:
//:
c13:BangBeanTest.java
//
<applet code=BangBeanTest
814
Thinking
in Java
//
width=400 height=500></applet>
import
bangbean.*;
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
public
class BangBeanTest extends JApplet {
JTextField
txt = new JTextField(20);
//
During testing, report actions:
class
BBL implements ActionListener {
int
count = 0;
public
void actionPerformed(ActionEvent e){
txt.setText("BangBean
action "+ count++);
}
}
public
void init() {
BangBean
bb = new BangBean();
try
{
bb.addActionListener(new
BBL());
}
catch(TooManyListenersException e) {
txt.setText("Too
many listeners");
}
Container
cp = getContentPane();
cp.add(bb);
cp.add(BorderLayout.SOUTH,
txt);
}
public
static void main(String[] args) {
Console.run(new
BangBeanTest(), 400, 500);
}
}
///:~
When
a Bean is in a development environment,
this class will not be
used,
but
it's helpful to provide a
rapid testing method for
each of your Beans.
BangBeanTest
places
a BangBean
within
the applet, attaching
a
simple
ActionListener
to
the BangBean
to
print an event count to
the
JTextField
whenever
an ActionEvent
occurs.
Usually, of course,
the
application
builder tool would create
most of the code that
uses the Bean.
Chapter
13: Creating Windows &
Applets
815
When
you run the BangBean
through
BeanDumper
or
put the
BangBean
inside
a Bean-enabled development environment,
you'll
notice
that there are many
more properties and actions
than are evident
from
the above code. That's
because BangBean
is
inherited from
JPanel,
and JPanel
is
also Bean, so you're seeing
its properties and
events
as well.
Packaging
a Bean
Before
you can bring a Bean
into a Bean-enabled visual
builder tool, it
must
be put into the standard
Bean container, which is a
JAR file that
includes
all the Bean classes as
well as a "manifest" file
that says "This is a
Bean."
A manifest file is simply a
text file that follows a
particular form.
For
the BangBean,
the manifest file looks
like this (without the
first and
last
lines):
//:!
:BangBean.mf
Manifest-Version:
1.0
Name:
bangbean/BangBean.class
Java-Bean:
True
///:~
The
first line indicates the
version of the manifest
scheme, which until
further
notice from Sun is 1.0.
The second line (empty
lines are ignored)
names
the BangBean.class
file,
and the third says,
"It's a Bean."
Without
the third line, the
program builder tool will
not recognize the
class
as a Bean.
The
only tricky part is that
you must make sure
that you get the
proper
path
in the "Name:" field. If you
look back at BangBean.java,
you'll see
it's
in package
bangbean (and
thus in a subdirectory called
"bangbean"
that's
off of the classpath), and
the name in the manifest
file must include
this
package information. In addition,
you must place the
manifest file in
the
directory above
the
root of your package path,
which in this case
means
placing the file in the
directory above the
"bangbean" subdirectory.
Then
you must invoke jar from
the same directory as the
manifest file, as
follows:
jar
cfm BangBean.jar BangBean.mf
bangbean
816
Thinking
in Java
This
assumes that you want
the resulting JAR file to be
named
BangBean.jar
and
that you've put the
manifest in a file
called
BangBean.mf.
You
might wonder "What about
all the other classes
that were generated
when
I compiled BangBean.java?"
Well, they all ended up
inside the
bangbean
subdirectory,
and you'll see that
the last argument for
the
above
jar command
line is the bangbean
subdirectory.
When you give
jar
the
name of a subdirectory, it packages
that entire subdirectory
into
the
jar file (including, in this
case, the original BangBean.java
source-
code
file--you might not choose
to include the source with
your own
Beans).
In addition, if you turn
around and unpack the
JAR file you've
just
created, you'll discover
that your manifest file
isn't inside, but that
jar
has
created its own manifest
file (based partly on yours)
called
MANIFEST.MF
and
placed it inside the
subdirectory META-INF
(for
"meta-information").
If you open this manifest
file you'll also notice
that
digital
signature information has
been added by jar
for
each file, of the
form:
Digest-Algorithms:
SHA MD5
SHA-Digest:
pDpEAG9NaeCx8aFtqPI4udSX/O0=
MD5-Digest:
O4NcS1hE3Smnzlp2hj6qeg==
In
general, you don't need to
worry about any of this,
and if you make
changes
you can just modify
your original manifest file
and reinvoke jar
to
create a new JAR file
for your Bean. You
can also add other
Beans to
the
JAR file simply by adding
their information to your
manifest.
One
thing to notice is that
you'll probably want to put
each Bean in its
own
subdirectory, since when you
create a JAR file you
hand the jar
utility
the name of a subdirectory
and it puts everything in
that
subdirectory
into the JAR file.
You can see that
both Frog
and
BangBean
are
in their own
subdirectories.
Once
you have your Bean
properly inside a JAR file
you can bring it into
a
Beans-enabled
program-builder environment. The
way you do this
varies
from
one tool to the next,
but Sun provides a freely
available test bed
for
JavaBeans
in their "Beans Development
Kit" (BDK) called the
"beanbox."
(Download
the BDK from java.sun.com/beans.)
To place your Bean in
the
Chapter
13: Creating Windows &
Applets
817
beanbox,
copy the JAR file
into the BDK's "jars"
subdirectory before
you
start
up the beanbox.
More
complex Bean support
You
can see how remarkably
simple it is to make a Bean.
But you aren't
limited
to what you've seen here.
The JavaBeans architecture
provides a
simple
point of entry but can
also scale to more complex
situations. These
situations
are beyond the scope of
this book, but they
will be briefly
introduced
here. You can find
more details at java.sun.com/beans.
One
place where you can
add sophistication is with
properties. The
examples
above have shown only
single properties, but it's
also possible to
represent
multiple properties in an array.
This is called an indexed
property.
You simply provide the
appropriate methods (again
following a
naming
convention for the method
names) and the Introspector
recognizes
an indexed property so your
application builder tool
can
respond
appropriately.
Properties
can be bound,
which means that they
will notify other
objects
via
a PropertyChangeEvent.
The other objects can
then choose to
change
themselves based on the
change to the Bean.
Properties
can be constrained,
which means that other
objects can veto a
change
to that property if it is unacceptable.
The other objects are
notified
using
a PropertyChangeEvent,
and they can throw
a
PropertyVetoException
to
prevent the change from
happening and to
restore
the old values.
You
can also change the
way your Bean is represented
at design time:
1.
You
can provide a custom
property sheet for your
particular Bean.
The
ordinary property sheet will
be used for all other
Beans, but
yours
is automatically invoked when
your Bean is
selected.
2.
You
can create a custom editor
for a particular property, so
the
ordinary
property sheet is used, but
when your special property
is
being
edited, your editor will
automatically be invoked.
818
Thinking
in Java
3.
You
can provide a custom
BeanInfo
class
for your Bean
that
produces
information that's different
from the default created
by
the
Introspector.
4.
It's
also possible to turn
"expert" mode on and off in
all
FeatureDescriptors
to distinguish between basic
features and
more
complicated ones.
More
to Beans
There's
another issue that couldn't
be addressed here. Whenever
you
create
a Bean, you should expect
that it will be run in a
multithreaded
environment.
This means that you
must understand the issues
of
threading,
which will be introduced in
Chapter 14. You'll find a
section
there
called "JavaBeans revisited"
that will look at the
problem and its
solution.
There
are a number of books about
JavaBeans; for example,
JavaBeans
by
Elliotte Rusty Harold (IDG,
1998).
Summary
Of
all the libraries in Java,
the GUI library has seen
the most dramatic
changes
from Java 1.0 to Java 2.
The Java 1.0 AWT
was roundly
criticized
as
being one of the worst
designs seen, and while it
would allow you to
create
portable programs, the
resulting GUI was "equally
mediocre on all
platforms."
It was also limiting,
awkward, and unpleasant to
use
compared
with the native application
development tools available on
a
particular
platform.
When
Java 1.1 introduced the
new event model and
JavaBeans, the stage
was
set--now it was possible to
create GUI components that
could be
easily
dragged and dropped inside
visual application builder
tools. In
addition,
the design of the event
model and Beans clearly
shows strong
consideration
for ease of programming and
maintainable code
(something
that
was not evident in the
1.0 AWT). But it wasn't
until the JFC/Swing
classes
appeared that the job
was finished. With the
Swing components,
cross-platform
GUI programming can be a civilized
experience.
Chapter
13: Creating Windows &
Applets
819
Actually,
the only thing that's
missing is the application
builder tool, and
this
is where the real revolution
lies. Microsoft's Visual
Basic and Visual
C++
require Microsoft's application
builder tools, as does
Borland's
Delphi
and C++ Builder. If you
want the application builder
tool to get
better,
you have to cross your
fingers and hope the
vendor will give
you
what
you want. But Java is an
open environment, and so not
only does it
allow
for competing application
builder environments, it
encourages
them.
And for these tools to be
taken seriously, they must
support
JavaBeans.
This means a leveled playing
field: if a better
application
builder
tool comes along, you're
not tied to the one
you've been using--
you
can pick up and move to
the new one and
increase your
productivity.
This
kind of competitive environment
for GUI application builder
tools
has
not been seen before,
and the resulting
marketplace can generate
only
positive
results for the productivity
of the programmer.
This
chapter was meant only to
give you an introduction to
the power of
Swing
and to get you started so
you could see how
relatively simple it is to
feel
your way through the
libraries. What you've seen
so far will probably
suffice
for a good portion of your
UI design needs. However,
there's a lot
more
to Swing--it's intended to be a fully
powered UI design tool
kit.
There's
probably a way to accomplish
just about everything you
can
imagine.
If
you don't see what
you need here, delve
into the online
documentation
from
Sun and search the
Web, and if that's not
enough then find a
dedicated
Swing book--a good place to
start is The
JFC Swing Tutorial,
by
Walrath & Campione (Addison
Wesley, 1999).
Exercises
Solutions
to selected exercises can be
found in the electronic
document The
Thinking in Java
Annotated
Solution Guide, available
for a small fee from
.
1.
Create
an applet/application using the
Console
class
as shown in
this
chapter. Include a text
field and three buttons.
When you
press
each button, make some
different text appear in the
text
field.
2.
Add
a check box to the applet
created in Exercise 1, capture
the
event,
and insert different text
into the text
field.
820
Thinking
in Java
3.
Create
an applet/application using Console.
In the HTML
documentation
from java.sun.com,
find the JPasswordField
and
add this to the program. If
the user types in the
correct
password,
use Joptionpane
to
provide a success message to
the
user.
4.
Create
an applet/application using Console,
and add all
the
components
that have an addActionListener(
) method.
(Look
these
up in the HTML documentation
from java.sun.com.
Hint:
use
the index.) Capture their
events and display an
appropriate
message
for each inside a text
field.
5.
Create
an applet/application using Console,
with a JButton
and
a
JTextField.
Write and attach the
appropriate listener so that
if
the
button has the focus,
characters typed into it
will appear in the
JTextField.
6.
Create
an applet/application using Console.
Add to the main
frame
all the components described
in this chapter,
including
menus
and a dialog box.
7.
Modify
TextFields.java
so
that the characters in
t2 retain
the
original
case that they were
typed in, instead of
automatically
being
forced to upper case.
8.
Locate
and download one or more of
the free GUI builder
development
environments available on the
Internet, or buy a
commercial
product. Discover what is
necessary to add
BangBean
to
this environment and to use
it.
9.
Add
Frog.class
to
the manifest file as shown
in this chapter and
run
jar to
create a JAR file containing
both Frog
and
BangBean.
Now
either download and install
the BDK from Sun or
use your
own
Beans-enabled program builder
tool and add the
JAR file to
your
environment so you can test
the two Beans.
10.
Create
your own JavaBean called
Valve that
contains two
properties:
a boolean
called
"on" and an int
called
"level." Create
a
manifest file, use jar to
package your Bean, then
load it into the
Chapter
13: Creating Windows &
Applets
821
beanbox
or into a Beans-enabled program
builder tool so that
you
can
test it.
11.
Modify
MessageBoxes.java
so
that it has an
individual
ActionListener
for
each button (instead of
matching the button
text).
12.
Monitor
a new type of event in
TrackEvent.java
by
adding the
new
event handling code. You'll
need to discover on your own
the
type
of event that you want to
monitor.
13.
Inherit
a new type of button from
JButton.
Each time you
press
this
button, it should change its
color to a randomly-selected
value.
See ColorBoxes.java
in
Chapter 14 for an example of
how
to
generate a random color
value.
14.
Modify
TextPane.java
to
use a JTextArea
instead
of a
JTextPane.
15.
Modify
Menus.java
to
use radio buttons instead of
check boxes
on
the menus.
16.
Simplify
List.java
by
passing the array to the
constructor and
eliminating
the dynamic addition of
elements to the list.
17.
Modify
SineWave.java
to
turn SineDraw
into
a JavaBean by
adding
"getter" and "setter"
methods.
18.
Remember
the "sketching box" toy
with two knobs, one
that
controls
the vertical movement of the
drawing point, and one
that
controls
the horizontal movement?
Create one of those,
using
SineWave.java
to
get you started. Instead of
knobs, use sliders.
Add
a button that will erase
the entire sketch.
19.
Create
an "asymptotic progress indicator"
that gets slower
and
slower
as it approaches the finish
point. Add random
erratic
behavior
so it will periodically look
like it's starting to speed
up.
20.
Modify
Progress.java
so
that it does not share
models, but
instead
uses a listener to connect
the slider and progress
bar.
822
Thinking
in Java
21.
Follow
the instructions in the
section titled "Packaging an
applet
into
a JAR file" to place
TicTacToe.java
into
a JAR file. Create
an
HTML page with the
(messy, complicated) version of
the applet
tag,
and modify it to use the
archive tag so as to use the
JAR file.
(Hint:
start with the HTML
page for TicTacToe.java
that
comes
with
this book's source-code
distribution.)
22.
Create
an applet/application using Console.
This should have
three
sliders, one each for
the red, green, and
blue values in
java.awt.Color.
The rest of the form
should be a JPanel
that
displays
the color determined by the
three sliders. Also
include
non-editable
text fields that show
the current RGB
values.
23.
In
the HTML documentation for
javax.swing,
look up the
JColorChooser.
Write a program with a
button that brings up
the
color chooser as a
dialog.
24.
Almost
every Swing component is
derived from Component,
which
has a setCursor(
) method.
Look this up in the
Java
HTML
documentation. Create an applet
and change the cursor
to
one
of the stock cursors in the
Cursor
class.
25.
Starting
with ShowAddListeners.java,
create a program with
the
full functionality of ShowMethodsClean.java
from
Chapter
12.
Chapter
13: Creating Windows &
Applets
823
14:
Multiple
Threads
Objects
provide a way to divide a
program into
independent
sections. Often, you also
need to turn a
program
into separate, independently
running subtasks.
Each
of these independent subtasks is
called a thread,
and you program
as
if each thread runs by
itself and has the
CPU to itself. Some
underlying
mechanism
is actually dividing up the
CPU time for you,
but in general,
you
don't have to think about
it, which makes programming
with multiple
threads
a much easier task.
A
process
is
a self-contained running program
with its own address
space.
A
multitasking
operating
system is capable of running
more than one
process
(program) at a time, while
making it look like each
one is
chugging
along on its own, by
periodically providing CPU
cycles to each
process.
A thread is a single sequential
flow of control within a
process. A
single
process can thus have
multiple concurrently executing
threads.
There
are many possible uses
for multithreading, but in
general, you'll
have
some part of your program
tied to a particular event or
resource, and
you
don't want to hang up the
rest of your program because
of that. So
you
create a thread associated
with that event or resource
and let it run
independently
of the main program. A good
example is a "quit"
button--
you
don't want to be forced to
poll the quit button in
every piece of code
you
write in your program and
yet you want the
quit button to be
responsive,
as if you were
checking
it regularly. In fact, one of
the most
immediately
compelling reasons for
multithreading is to produce a
responsive
user interface.
825
Responsive
user interfaces
As
a starting point, consider a
program that performs some
CPU-intensive
operation
and thus ends up ignoring
user input and being
unresponsive.
This
one, a combined applet/application,
will simply display the
result of
a
running counter:
//:
c14:Counter1.java
//
A non-responsive user interface.
//
<applet code=Counter1 width=300
height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.event.*;
import
java.awt.*;
import
com.bruceeckel.swing.*;
public
class Counter1 extends JApplet {
private
int count = 0;
private
JButton
start
= new JButton("Start"),
onOff
= new JButton("Toggle");
private
JTextField t = new JTextField(10);
private
boolean runFlag = true;
public
void init() {
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
start.addActionListener(new
StartL());
cp.add(start);
onOff.addActionListener(new
OnOffL());
cp.add(onOff);
}
public
void go() {
while
(true) {
try
{
Thread.sleep(100);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
if
(runFlag)
826
Thinking
in Java
t.setText(Integer.toString(count++));
}
}
class
StartL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
go();
}
}
class
OnOffL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
runFlag
= !runFlag;
}
}
public
static void main(String[] args) {
Console.run(new
Counter1(), 300, 100);
}
}
///:~
At
this point, the Swing
and applet code should be
reasonably familiar
from
Chapter 13. The go( ) method
is where the program stays
busy: it
puts
the current value of
count
into
the JTextField
t,
then increments
count.
Part
of the infinite loop inside
go( ) is
to call sleep(
).
sleep( )
must
be
associated
with a Thread
object,
and it turns out that
every application
has
some
thread
associated with it. (Indeed,
Java is based on threads
and
there
are always some running
along with your
application.) So regardless
of
whether you're explicitly
using threads, you can
produce the current
thread
used by your program with
Thread
and
the static sleep(
)
method.
Note
that sleep(
) can
throw an InterruptedException,
although
throwing
such an exception is considered a
hostile way to break from
a
thread
and should be discouraged.
(Once again, exceptions are
for
exceptional
conditions, not normal flow
of control.) Interrupting a
sleeping
thread is included to support a
future language
feature.
When
the start
button
is pressed, go(
) is
invoked. On examining go(
),
you
might naively think (as I
did) that it should allow
multithreading
because
it goes to sleep. That is,
while the method is asleep,
it seems like
the
CPU could be busy monitoring
other button presses. But it
turns out
Chapter
14: Multiple
Threads
827
that
the real problem is that
go( ) never
returns, since it's in an
infinite
loop,
and this means that
actionPerformed(
) never
returns. Since
you're
stuck inside actionPerformed(
) for
the first keypress,
the
program
can't handle any other
events. (To get out,
you must somehow
kill
the process; the easiest
way to do this is to press
Control-C in the
console
window, if you started it
from the console. If you
start it via the
browser,
you have to kill the
browser window.)
The
basic problem here is that
go( ) needs
to continue performing
its
operations,
and at the same time it
needs to return so
that
actionPerformed(
) can
complete and the user
interface can
continue
responding
to the user. But in a
conventional method like
go( ) it
cannot
continue
and
at
the same time return
control to the rest of the
program.
This
sounds like an impossible
thing to accomplish, as if the
CPU must be
in
two places at once, but
this is precisely the
illusion that
threading
provides.
The
thread model (and its
programming support in Java) is
a
programming
convenience to simplify juggling
several operations at
the
same
time within a single
program. With threads, the
CPU will pop
around
and give each thread
some of its time. Each
thread has the
consciousness
of constantly having the CPU
to itself, but the CPU's
time is
actually
sliced between all the
threads. The exception to
this is if your
program
is running on multiple CPUs.
But one of the great
things about
threading
is that you are abstracted
away from this layer, so
your code
does
not need to know whether it
is actually running on a single
CPU or
many.
Thus, threads are a way to
create transparently scalable
programs.
Threading
reduces computing efficiency
somewhat, but the
net
improvement
in program design, resource
balancing, and user
convenience
is often quite valuable. Of
course, if you have more
than one
CPU,
then the operating system
can dedicate each CPU to a
set of threads
or
even a single thread and
the whole program can
run much faster.
Multitasking
and multithreading tend to be
the most reasonable ways
to
utilize
multiprocessor systems.
Inheriting
from Thread
The
simplest way to create a
thread is to inherit from
class Thread,
which
has all the wiring
necessary to create and run
threads. The most
828
Thinking
in Java
important
method for Thread
is
run( ),
which you must override
to
make
the thread do your bidding.
Thus, run(
) is
the code that will
be
executed
"simultaneously" with the
other threads in a
program.
The
following example creates
any number of threads that
it keeps track
of
by assigning each thread a
unique number, generated
with a static
variable.
The Thread's
run( )
method
is overridden to count down
each
time
it passes through its loop
and to finish when the
count is zero (at
the
point
when run(
) returns,
the thread is
terminated).
//:
c14:SimpleThread.java
//
Very simple Threading example.
public
class SimpleThread extends Thread {
private
int countDown = 5;
private
static int threadCount = 0;
private
int threadNumber = ++threadCount;
public
SimpleThread() {
System.out.println("Making
" + threadNumber);
}
public
void run() {
while(true)
{
System.out.println("Thread
" +
threadNumber
+ "(" + countDown + ")");
if(--countDown
== 0) return;
}
}
public
static void main(String[] args) {
for(int
i = 0; i < 5; i++)
new
SimpleThread().start();
System.out.println("All
Threads Started");
}
}
///:~
A
run( )
method
virtually always has some
kind of loop that
continues
until
the thread is no longer
necessary, so you must
establish the
condition
on which to break out of
this loop (or, in the
case above, simply
return
from
run( )).
Often, run(
) is
cast in the form of an
infinite loop,
which
means that, barring some
external factor that causes
run( )
to
terminate,
it will continue
forever.
Chapter
14: Multiple
Threads
829
In
main( )
you
can see a number of threads
being created and run.
The
start(
) method
in the Thread
class
performs special initialization
for
the
thread and then calls
run( ).
So the steps are: the
constructor is called
to
build the object, then
start( )
configures
the thread and calls
run( ).
If
you
don't call start(
) (which
you can do in the
constructor, if that's
appropriate)
the thread will never be
started.
The
output for one run of
this program (it will be
different from one
run
to
another) is:
Making
1
Making
2
Making
3
Making
4
Making
5
Thread
1(5)
Thread
1(4)
Thread
1(3)
Thread
1(2)
Thread
2(5)
Thread
2(4)
Thread
2(3)
Thread
2(2)
Thread
2(1)
Thread
1(1)
All
Threads Started
Thread
3(5)
Thread
4(5)
Thread
4(4)
Thread
4(3)
Thread
4(2)
Thread
4(1)
Thread
5(5)
Thread
5(4)
Thread
5(3)
Thread
5(2)
Thread
5(1)
Thread
3(4)
Thread
3(3)
Thread
3(2)
Thread
3(1)
830
Thinking
in Java
You'll
notice that nowhere in this
example is sleep(
) called,
and yet the
output
indicates that each thread
gets a portion of the CPU's
time in
which
to execute. This shows that
sleep(
),
while it relies on the
existence
of
a thread in order to execute, is
not involved with either
enabling or
disabling
threading. It's simply
another method.
You
can also see that
the threads are not
run in the order that
they're
created.
In fact, the order that
the CPU attends to an
existing set of
threads
is indeterminate, unless you go in
and adjust the priorities
using
Thread's
setPriority(
) method.
When
main( )
creates
the Thread
objects
it isn't capturing
the
references
for any of them. An ordinary
object would be fair game
for
garbage
collection, but not a
Thread.
Each Thread
"registers"
itself so
there
is actually a reference to it someplace
and the garbage collector
can't
clean
it up.
Threading
for a responsive
interface
Now
it's possible to solve the
problem in Counter1.java
with
a thread.
The
trick is to place the
subtask--that is, the loop
that's inside go(
)--
inside
the run(
) method
of a thread. When the user
presses the start
button,
the thread is started, but
then the creation
of
the thread
completes,
so even though the thread is
running, the main job of
the
program
(watching for and responding
to user-interface events)
can
continue.
Here's the solution:
//:
c14:Counter2.java
//
A responsive user interface with threads.
//
<applet code=Counter2 width=300
height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Counter2 extends JApplet {
private
class SeparateSubTask extends Thread {
Chapter
14: Multiple
Threads
831
private
int count = 0;
private
boolean runFlag = true;
SeparateSubTask()
{ start(); }
void
invertFlag() { runFlag = !runFlag; }
public
void run() {
while
(true) {
try
{
sleep(100);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
}
private
SeparateSubTask sp = null;
private
JTextField t = new JTextField(10);
private
JButton
start
= new JButton("Start"),
onOff
= new JButton("Toggle");
class
StartL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
if(sp
== null)
sp
= new SeparateSubTask();
}
}
class
OnOffL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
if(sp
!= null)
sp.invertFlag();
}
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
start.addActionListener(new
StartL());
cp.add(start);
onOff.addActionListener(new
OnOffL());
cp.add(onOff);
832
Thinking
in Java
}
public
static void main(String[] args) {
Console.run(new
Counter2 (), 300, 100);
}
}
///:~
Counter2
is
a straightforward program, whose
only job is to set up
and
maintain
the user interface. But
now, when the user
presses the start
button,
the event-handling code does
not call a method. Instead a
thread
of
class SeparateSubTask
is
created, and then the
Counter2
event
loop
continues.
The
class SeparateSubTask
is
a simple extension of Thread
with
a
constructor
that runs the thread by
calling start(
),
and a run(
) that
essentially
contains the "go(
)"
code from Counter1.java.
Because
SeparateSubTask
is
an inner class, it can
directly access the
JTextField
t in
Counter2;
you can see this
happening inside run(
).
The
t field
in the outer class is
private
since
SeparateSubTask
can
access
it without getting any
special permission--and it's
always good to
make
fields "as private
as
possible" so they cannot be
accidentally
changed
by forces outside your
class.
When
you press the onOff
button
it toggles the runFlag
inside
the
SeparateSubTask
object.
That thread (when it looks
at the flag) can
then
start and stop itself.
Pressing the onOff
button
produces an
apparently
instant response. Of course,
the response isn't really
instant,
not
like that of a system that's
driven by interrupts. The
counter stops
only
when the thread has
the CPU and notices
that the flag has
changed.
You
can see that the
inner class SeparateSubTask
is
private,
which
means
that its fields and
methods can be given default
access (except for
run(
),
which must be public
since
it is public
in
the base class).
The
private
inner
class is not accessible to
anyone but Counter2,
and the
two
classes are tightly coupled.
Anytime you notice classes
that appear to
have
high coupling with each
other, consider the coding
and maintenance
improvements
you might get by using
inner classes.
Chapter
14: Multiple
Threads
833
Combining
the thread
with
the main class
In
the example above you
can see that the
thread class is separate
from
the
program's main class. This
makes a lot of sense and is
relatively easy
to
understand. There is,
however, an alternate form
that you will often
see
used
that is not so clear but is
usually more concise (which
probably
accounts
for its popularity). This
form combines the main
program class
with
the thread class by making
the main program class a
thread. Since
for
a GUI program the main
program class must be
inherited from either
Frame
or
Applet,
an interface must be used to
paste on the
additional
functionality.
This interface is called
Runnable,
and it contains the
same
basic
method that Thread
does.
In fact, Thread
also
implements
Runnable,
which specifies only that
there be a run(
) method.
The
use
of
the combined program/thread is
not quite so obvious.
When
you
start the program, you
create an object that's
Runnable,
but you
don't
start the thread. This
must be done explicitly. You
can see this in
the
following
program, which reproduces
the functionality of Counter2:
//:
c14:Counter3.java
//
Using the Runnable interface to turn the
//
main class into a thread.
//
<applet code=Counter3 width=300
height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Counter3
extends
JApplet implements Runnable {
private
int count = 0;
private
boolean runFlag = true;
private
Thread selfThread = null;
private
JButton
start
= new JButton("Start"),
onOff
= new JButton("Toggle");
private
JTextField t = new JTextField(10);
public
void run() {
834
Thinking
in Java
while
(true) {
try
{
selfThread.sleep(100);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
class
StartL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
if(selfThread
== null) {
selfThread
= new Thread(Counter3.this);
selfThread.start();
}
}
}
class
OnOffL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
runFlag
= !runFlag;
}
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
start.addActionListener(new
StartL());
cp.add(start);
onOff.addActionListener(new
OnOffL());
cp.add(onOff);
}
public
static void main(String[] args) {
Console.run(new
Counter3(), 300, 100);
}
}
///:~
Now
the run(
) is
inside the class, but
it's still dormant after
init(
)
completes.
When you press the
start
button,
the thread is created (if
it
doesn't
already exist) in the
somewhat obscure
expression:
Chapter
14: Multiple
Threads
835
new
Thread(Counter3.this);
When
something has a Runnable
interface,
it simply means that it has
a
run(
) method,
but there's nothing special
about that--it doesn't
produce
any
innate threading abilities,
like those of a class
inherited from
Thread.
So to produce a thread from a
Runnable
object,
you must
create
a separate Thread
object
as shown above, handing the
Runnable
object
to the special Thread
constructor.
You can then call
start( )
for
that
thread:
selfThread.start();
This
performs the usual
initialization and then
calls run(
).
The
convenient aspect about the
Runnable
interface is that
everything
belongs
to the same class. If you
need to access something,
you simply do
it
without going through a
separate object. However, as
you saw in the
previous
example, this access is just
as easy using an inner
class1.
Making
many threads
Consider
the creation of many
different threads. You can't
do this with the
previous
example, so you must go back
to having separate
classes
inherited
from Thread
to
encapsulate the run(
).
But this is a more
general
solution and easier to
understand, so while the
previous example
shows
a coding style you'll often
see, I can't recommend it
for most cases
because
it's just a little bit
more confusing and less
flexible.
The
following example repeats
the form of the examples
above with
counters
and toggle buttons. But
now all the information
for a particular
counter,
including the button and
text field, is inside its
own object that is
inherited
from Thread.
All the fields in Ticker
are
private,
which
means
that the Ticker
implementation
can be changed at will,
including
the
quantity and type of data
components to acquire and
display
1
Runnable
was
in Java 1.0, while inner classes
were not introduced until
Java 1.1, which
may
partially account for the
existence of Runnable.
Also, traditional multithreading
architectures
focused on a function to be run
rather than an object. My
preference is
always
to inherit from Thread
if
I can; it seems cleaner and more
flexible to me.
836
Thinking
in Java
information.
When a Ticker
object
is created, the constructor
adds its
visual
components to the content
pane of the outer
object:
//:
c14:Counter4.java
//
By keeping your thread as a distinct class,
//
you can have as many threads as you want.
//
<applet code=Counter4 width=200
height=600>
//
<param name=size
value="12"></applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Counter4 extends JApplet {
private
JButton start = new JButton("Start");
private
boolean started = false;
private
Ticker[] s;
private
boolean isApplet = true;
private
int size = 12;
class
Ticker extends Thread {
private
JButton b = new JButton("Toggle");
private
JTextField t = new JTextField(10);
private
int count = 0;
private
boolean runFlag = true;
public
Ticker() {
b.addActionListener(new
ToggleL());
JPanel
p = new JPanel();
p.add(t);
p.add(b);
//
Calls JApplet.getContentPane().add():
getContentPane().add(p);
}
class
ToggleL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
runFlag
= !runFlag;
}
}
public
void run() {
while
(true) {
if
(runFlag)
t.setText(Integer.toString(count++));
Chapter
14: Multiple
Threads
837
try
{
sleep(100);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class
StartL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
if(!started)
{
started
= true;
for
(int i = 0; i < s.length; i++)
s[i].start();
}
}
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
//
Get parameter "size" from Web page:
if
(isApplet) {
String
sz = getParameter("size");
if(sz
!= null)
size
= Integer.parseInt(sz);
}
s
= new Ticker[size];
for
(int i = 0; i < s.length; i++)
s[i]
= new Ticker();
start.addActionListener(new
StartL());
cp.add(start);
}
public
static void main(String[] args) {
Counter4
applet = new Counter4();
//
This isn't an applet, so set the flag and
//
produce the parameter values from args:
applet.isApplet
= false;
if(args.length
!= 0)
applet.size
= Integer.parseInt(args[0]);
Console.run(applet,
200, applet.size * 50);
}
838
Thinking
in Java
}
///:~
Ticker
contains
not only its threading
equipment but also the
way to
control
and display the thread.
You can create as many
threads as you
want
without explicitly creating
the windowing
components.
In
Counter4
there's
an array of Ticker
objects
called s.
For maximum
flexibility,
the size of this array is
initialized by reaching out
into the Web
page
using applet parameters.
Here's what the size
parameter looks like
on
the page, embedded inside
the applet tag:
<param
name=size value="20">
The
param,
name,
and value
are
all HTML keywords. name is
what
you'll
be referring to in your program,
and value
can
be any string, not
just
something that resolves to a
number.
You'll
notice that the
determination of the size of
the array s
is
done
inside
init(
),
and not as part of an inline
definition of s.
That is, you
cannot
say
as part of the class
definition (outside of any
methods):
int
size = Integer.parseInt(getParameter("size"));
Ticker[]
s = new Ticker[size];
You
can compile this, but
you'll get a strange
"null-pointer exception" at
run-time.
It works fine if you move
the getParameter(
) initialization
inside
of init(
).
The applet framework
performs the necessary
startup to
grab
the parameters before
entering init(
).
In
addition, this code is set
up to be either an applet or an
application.
When
it's an application the
size
argument
is extracted from the
command
line (or a default value is
provided).
Once
the size of the array is
established, new Ticker
objects
are created;
as
part of the Ticker
constructor
the button and text
field for each
Ticker
is
added to the applet.
Pressing
the start
button
means looping through the
entire array of
Tickers
and calling start(
) for
each one. Remember,
start( )
performs
necessary
thread initialization and
then calls run(
) for
that thread.
The
ToggleL
listener
simply inverts the flag in
Ticker
and
when the
associated
thread next takes note it
can react
accordingly.
Chapter
14: Multiple
Threads
839
One
value of this example is
that it allows you to easily
create large sets of
independent
subtasks and to monitor
their behavior. In this
case, you'll
see
that as the number of
subtasks gets larger, your
machine will probably
show
more divergence in the
displayed numbers because of
the way that
the
threads are served.
You
can also experiment to
discover how important the
sleep(100)
is
inside
Ticker.run(
).
If you remove the sleep(
),
things will work
fine
until
you press a toggle button.
Then that particular thread
has a false
runFlag
and
the run(
) is
just tied up in a tight
infinite loop, which
appears
difficult to break during
multithreading, so the
responsiveness
and
speed of the program really
bogs down.
Daemon
threads
A
"daemon" thread is one that
is supposed to provide a general
service in
the
background as long as the
program is running, but is
not part of the
essence
of the program. Thus, when
all of the non-daemon
threads
complete,
the program is terminated.
Conversely, if there are any
non-
daemon
threads still running, the
program doesn't terminate.
(There is,
for
instance, a thread that runs
main(
).)
You
can find out if a thread is
a daemon by calling isDaemon(
),
and you
can
turn the "daemonhood" of a
thread on and off with
setDaemon(
).
If
a
thread is a daemon, then any
threads it creates will
automatically be
daemons.
The
following example demonstrates
daemon threads:
//:
c14:Daemons.java
//
Daemonic behavior.
import
java.io.*;
class
Daemon extends Thread {
private
static final int SIZE = 10;
private
Thread[] t = new Thread[SIZE];
public
Daemon() {
setDaemon(true);
start();
}
public
void run() {
840
Thinking
in Java
for(int
i = 0; i < SIZE; i++)
t[i]
= new DaemonSpawn(i);
for(int
i = 0; i < SIZE; i++)
System.out.println(
"t["
+ i + "].isDaemon() = "
+
t[i].isDaemon());
while(true)
yield();
}
}
class
DaemonSpawn extends Thread {
public
DaemonSpawn(int i) {
System.out.println(
"DaemonSpawn
" + i + " started");
start();
}
public
void run() {
while(true)
yield();
}
}
public
class Daemons {
public
static void main(String[] args)
throws
IOException {
Thread
d = new Daemon();
System.out.println(
"d.isDaemon()
= " + d.isDaemon());
//
Allow the daemon threads to
//
finish their startup processes:
System.out.println("Press
any key");
System.in.read();
}
}
///:~
The
Daemon
thread
sets its daemon flag to
"true" and then spawns
a
bunch
of other threads to show
that they are also
daemons. Then it goes
into
an infinite loop that calls
yield( )
to
give up control to the
other
processes.
In an earlier version of this
program, the infinite loops
would
Chapter
14: Multiple
Threads
841
increment
int counters,
but this seemed to bring
the whole program to
a
stop.
Using yield(
) makes
the program quite
peppy.
There's
nothing to keep the program
from terminating once
main(
)
finishes
its job, since there
are nothing but daemon
threads running. So
that
you can see the
results of starting all the
daemon threads, System.in
is
set up to read so the
program waits for a keypress
before terminating.
Without
this you see only
some of the results from
the creation of the
daemon
threads. (Try replacing the
read( )
code
with sleep(
) calls
of
various
lengths to see this
behavior.)
Sharing
limited resources
You
can think of a single-threaded
program as one lonely entity
moving
around
through your problem space
and doing one thing at a
time.
Because
there's only one entity,
you never have to think
about the
problem
of two entities trying to
use the same resource at
the same time,
like
two people trying to park in
the same space, walk
through a door at
the
same time, or even talk at
the same time.
With
multithreading, things aren't
lonely anymore, but you
now have the
possibility
of two or more threads
trying to use the same
limited resource
at
once. Colliding over a
resource must be prevented or
else you'll have
two
threads trying to access the
same bank account at the
same time,
print
to the same printer, or
adjust the same valve,
etc.
Improperly
accessing resources
Consider
a variation on the counters
that have been used so
far in this
chapter.
In the following example,
each thread contains two
counters that
are
incremented and displayed
inside run(
).
In addition, there's
another
thread
of class Watcher
that
is watching the counters to
see if they're
always
equivalent. This seems like
a needless activity, since
looking at the
code
it appears obvious that the
counters will always be the
same. But
that's
where the surprise comes
in. Here's the first
version of the
program:
//:
c14:Sharing1.java
//
Problems with resource sharing while threading.
//
<applet code=Sharing1 width=350
height=500>
842
Thinking
in Java
//
<param name=size value="12">
//
<param name=watchers
value="15">
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Sharing1 extends JApplet {
private
static int accessCount = 0;
private
static JTextField aCount =
new
JTextField("0", 7);
public
static void incrementAccess() {
accessCount++;
aCount.setText(Integer.toString(accessCount));
}
private
JButton
start
= new JButton("Start"),
watcher
= new JButton("Watch");
private
boolean isApplet = true;
private
int numCounters = 12;
private
int numWatchers = 15;
private
TwoCounter[] s;
class
TwoCounter extends Thread {
private
boolean started = false;
private
JTextField
t1
= new JTextField(5),
t2
= new JTextField(5);
private
JLabel l =
new
JLabel("count1 == count2");
private
int count1 = 0, count2 = 0;
//
Add the display components as a panel:
public
TwoCounter() {
JPanel
p = new JPanel();
p.add(t1);
p.add(t2);
p.add(l);
getContentPane().add(p);
}
public
void start() {
if(!started)
{
Chapter
14: Multiple
Threads
843
started
= true;
super.start();
}
}
public
void run() {
while
(true) {
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
try
{
sleep(500);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public
void synchTest() {
Sharing1.incrementAccess();
if(count1
!= count2)
l.setText("Unsynched");
}
}
class
Watcher extends Thread {
public
Watcher() { start(); }
public
void run() {
while(true)
{
for(int
i = 0; i < s.length; i++)
s[i].synchTest();
try
{
sleep(500);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class
StartL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
for(int
i = 0; i < s.length; i++)
s[i].start();
}
}
844
Thinking
in Java
class
WatcherL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
for(int
i = 0; i < numWatchers; i++)
new
Watcher();
}
}
public
void init() {
if(isApplet)
{
String
counters = getParameter("size");
if(counters
!= null)
numCounters
= Integer.parseInt(counters);
String
watchers = getParameter("watchers");
if(watchers
!= null)
numWatchers
= Integer.parseInt(watchers);
}
s
= new TwoCounter[numCounters];
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
for(int
i = 0; i < s.length; i++)
s[i]
= new TwoCounter();
JPanel
p = new JPanel();
start.addActionListener(new
StartL());
p.add(start);
watcher.addActionListener(new
WatcherL());
p.add(watcher);
p.add(new
JLabel("Access Count"));
p.add(aCount);
cp.add(p);
}
public
static void main(String[] args) {
Sharing1
applet = new Sharing1();
//
This isn't an applet, so set the flag and
//
produce the parameter values from args:
applet.isApplet
= false;
applet.numCounters
=
(args.length
== 0 ? 12 :
Integer.parseInt(args[0]));
applet.numWatchers
=
(args.length
< 2 ? 15 :
Integer.parseInt(args[1]));
Console.run(applet,
350,
Chapter
14: Multiple
Threads
845
applet.numCounters
* 50);
}
}
///:~
As
before, each counter
contains its own display
components: two text
fields
and a label that initially
indicates that the counts
are equivalent.
These
components are added to the
content pane of the outer
class object
in
the TwoCounter
constructor.
Because
a TwoCounter
thread
is started via a keypress by
the user, it's
possible
that start(
) could
be called more than once.
It's illegal for
Thread.start(
) to
be called more than once
for a thread (an exception
is
thrown).
You can see the
machinery to prevent this in
the started
flag
and
the overridden start(
) method.
In
run( ),
count1
and
count2
are
incremented and displayed in
a
manner
that would seem to keep
them identical. Then
sleep( )
is
called;
without
this call the program
balks because it becomes
hard for the
CPU
to
swap tasks.
The
synchTest(
) method
performs the apparently
useless activity of
checking
to see if count1
is
equivalent to count2;
if they are not
equivalent
it sets the label to
"Unsynched" to indicate this.
But first, it
calls
a static member of the class
Sharing1
that
increments and
displays
an
access counter to show how
many times this check
has occurred
successfully.
(The reason for this
will become apparent in
later variations
of
this example.)
The
Watcher
class
is a thread whose job is to
call synchTest(
) for
all of
the
TwoCounter
objects
that are active. It does
this by stepping
through
the
array that's kept in the
Sharing1
object.
You can think of
the
Watcher
as
constantly peeking over the
shoulders of the TwoCounter
objects.
Sharing1
contains
an array of TwoCounter
objects
that it initializes in
init(
) and
starts as threads when you
press the "start" button.
Later,
when
you press the "Watch"
button, one or more watchers
are created and
freed
upon the unsuspecting
TwoCounter
threads.
Note
that to run this as an
applet in a browser, your
applet tag will need
to
contain
the lines:
846
Thinking
in Java
<param
name=size value="20">
<param
name=watchers value="1">
You
can experiment by changing
the width, height, and
parameters to suit
your
tastes. By changing the
size
and
watchers
you'll
change the
behavior
of the program. This program
is set up to run as a
stand-alone
application
by pulling the arguments
from the command line
(or
providing
defaults).
Here's
the surprising part. In
TwoCounter.run(
),
the infinite loop is
just
repeatedly passing over the
adjacent lines:
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
(as
well as sleeping, but that's
not important here). When
you run the
program,
however, you'll discover
that count1
and
count2
will
be
observed
(by the Watchers)
to be unequal at times! This is
because of the
nature
of threads--they can be suspended at
any time. So at times,
the
suspension
occurs between
the
execution of the above two
lines, and the
Watcher
thread
happens to come along and
perform the comparison
at
just
this moment, thus finding
the two counters to be
different.
This
example shows a fundamental
problem with using threads.
You
never
know when a thread might be
run. Imagine sitting at a
table with a
fork,
about to spear the last
piece of food on your plate
and as your fork
reaches
for it, the food
suddenly vanishes (because
your thread was
suspended
and another thread came in
and stole the food).
That's the
problem
that you're dealing
with.
Sometimes
you don't care if a resource
is being accessed at the
same time
you're
trying to use it (the food
is on some other plate). But
for
multithreading
to work, you need some
way to prevent two threads
from
accessing
the same resource, at least
during critical
periods.
Preventing
this kind of collision is
simply a matter of putting a
lock on a
resource
when one thread is using
it. The first thread
that accesses a
resource
locks it, and then
the other threads cannot
access that resource
until
it is unlocked, at which time
another thread locks and
uses it, etc. If
the
front seat of the car is
the limited resource, the
child who shouts
"Dibs!"
asserts the lock.
Chapter
14: Multiple
Threads
847
How
Java shares resources
Java
has built-in support to
prevent collisions over one
kind of resource:
the
memory in an object. Since
you typically make the
data elements of a
class
private
and
access that memory only
through methods, you
can
prevent
collisions by making a particular
method synchronized.
Only
one
thread at a time can call a
synchronized
method
for a particular
object
(although that thread can
call more than one of
the object's
synchronized
methods). Here are simple
synchronized
methods:
synchronized
void f() { /* ... */ }
synchronized
void g(){ /* ... */ }
Each
object contains a single
lock (also called a
monitor)
that is
automatically
part of the object (you
don't have to write any
special code).
When
you call any synchronized
method,
that object is locked and
no
other
synchronized
method
of that object can be called
until the first
one
finishes and releases the
lock. In the example above,
if f(
) is
called
for
an object, g(
) cannot
be called for the same
object until f(
) is
completed
and releases the lock.
Thus, there's a single lock
that's shared
by
all the synchronized
methods
of a particular object, and
this lock
prevents
common memory from being
written by more than one
method
at
a time (i.e., more than
one thread at a
time).
There's
also a single lock per
class (as part of the
Class
object
for the
class),
so that synchronized
static methods
can lock each other
out
from
simultaneous access of static
data
on a class-wide basis.
Note
that if you want to guard
some other resource from
simultaneous
access
by multiple threads, you can
do so by forcing access to
that
resource
through synchronized
methods.
Synchronizing
the counters
Armed
with this new keyword it
appears that the solution is
at hand: we'll
simply
use the synchronized
keyword
for the methods in
TwoCounter.
The following example is the
same as the previous
one,
with
the addition of the new
keyword:
//:
c14:Sharing2.java
//
Using the synchronized keyword to prevent
848
Thinking
in Java
//
multiple access to a particular resource.
//
<applet code=Sharing2 width=350
height=500>
//
<param name=size value="12">
//
<param name=watchers
value="15">
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Sharing2 extends JApplet {
TwoCounter[]
s;
private
static int accessCount = 0;
private
static JTextField aCount =
new
JTextField("0", 7);
public
static void incrementAccess() {
accessCount++;
aCount.setText(Integer.toString(accessCount));
}
private
JButton
start
= new JButton("Start"),
watcher
= new JButton("Watch");
private
boolean isApplet = true;
private
int numCounters = 12;
private
int numWatchers = 15;
class
TwoCounter extends Thread {
private
boolean started = false;
private
JTextField
t1
= new JTextField(5),
t2
= new JTextField(5);
private
JLabel l =
new
JLabel("count1 == count2");
private
int count1 = 0, count2 = 0;
public
TwoCounter() {
JPanel
p = new JPanel();
p.add(t1);
p.add(t2);
p.add(l);
getContentPane().add(p);
}
Chapter
14: Multiple
Threads
849
public
void start() {
if(!started)
{
started
= true;
super.start();
}
}
public
synchronized void run() {
while
(true) {
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
try
{
sleep(500);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public
synchronized void synchTest() {
Sharing2.incrementAccess();
if(count1
!= count2)
l.setText("Unsynched");
}
}
class
Watcher extends Thread {
public
Watcher() { start(); }
public
void run() {
while(true)
{
for(int
i = 0; i < s.length; i++)
s[i].synchTest();
try
{
sleep(500);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class
StartL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
for(int
i = 0; i < s.length; i++)
850
Thinking
in Java
s[i].start();
}
}
class
WatcherL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
for(int
i = 0; i < numWatchers; i++)
new
Watcher();
}
}
public
void init() {
if(isApplet)
{
String
counters = getParameter("size");
if(counters
!= null)
numCounters
= Integer.parseInt(counters);
String
watchers = getParameter("watchers");
if(watchers
!= null)
numWatchers
= Integer.parseInt(watchers);
}
s
= new TwoCounter[numCounters];
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
for(int
i = 0; i < s.length; i++)
s[i]
= new TwoCounter();
JPanel
p = new JPanel();
start.addActionListener(new
StartL());
p.add(start);
watcher.addActionListener(new
WatcherL());
p.add(watcher);
p.add(new
Label("Access Count"));
p.add(aCount);
cp.add(p);
}
public
static void main(String[] args) {
Sharing2
applet = new Sharing2();
//
This isn't an applet, so set the flag and
//
produce the parameter values from args:
applet.isApplet
= false;
applet.numCounters
=
(args.length
== 0 ? 12 :
Integer.parseInt(args[0]));
applet.numWatchers
=
Chapter
14: Multiple
Threads
851
(args.length
< 2 ? 15 :
Integer.parseInt(args[1]));
Console.run(applet,
350,
applet.numCounters
* 50);
}
}
///:~
You'll
notice that both
run( )
and
synchTest(
) are
synchronized.
If
you
synchronize only one of the
methods, then the other is
free to ignore
the
object lock and can be
called with impunity. This
is an important
point:
Every method that accesses a
critical shared resource
must be
synchronized
or
it won't work right.
Now
a new issue arises. The
Watcher
can
never get a peek at
what's
going
on because the entire
run( )
method
has been synchronized,
and
since
run( )
is
always running for each
object the lock is always
tied up
and
synchTest(
) can
never be called. You can
see this because
the
accessCount
never
changes.
What
we'd like for this
example is a way to isolate
only part
of
the code
inside
run( ).
The section of code you
want to isolate this way is
called a
critical
section and
you use the synchronized
keyword
in a different
way
to set up a critical section.
Java supports critical
sections with the
synchronized
block; this
time synchronized
is
used to specify the
object
whose
lock is being used to
synchronize the enclosed
code:
synchronized(syncObject)
{
//
This code can be accessed
//
by only one thread at a time
}
Before
the synchronized block can
be entered, the lock must be
acquired
on
syncObject.
If some other thread already
has this lock, then
the block
cannot
be entered until the lock is
given up.
The
Sharing2
example
can be modified by removing
the synchronized
keyword
from the entire run( )
method
and instead putting a
synchronized
block
around the two critical
lines. But what
object
should
be used as the lock? The
one that is already
respected by
synchTest(
),
which is the current object
(this)!
So the modified run(
)
looks
like this:
852
Thinking
in Java
public
void run() {
while
(true) {
synchronized(this)
{
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
}
try
{
sleep(500);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
This
is the only change that
must be made to Sharing2.java,
and you'll
see
that while the two
counters are never out of
synch (according to
when
the
Watcher
is
allowed to look at them),
there is still adequate
access
provided
to the Watcher
during
the execution of run(
).
Of
course, all synchronization
depends on programmer diligence:
every
piece
of code that can access a
shared resource must be
wrapped in an
appropriate
synchronized block.
Synchronized
efficiency
Since
having two methods write to
the same piece of data
never
sounds
like
a particularly good idea, it
might seem to make sense
for all methods
to
be automatically synchronized
and
eliminate the synchronized
keyword
altogether. (Of course, the
example with a synchronized
run(
) shows
that this wouldn't work
either.) But it turns out
that
acquiring
a lock is not a cheap
operation--it multiplies the
cost of a
method
call (that is, entering
and exiting from the
method, not executing
the
body of the method) by a
minimum of four times, and
could be much
more
depending on your implementation. So if
you know that a
particular
method
will not cause contention
problems it is expedient to leave
off the
synchronized
keyword.
On the other hand, leaving
off the
synchronized
keyword
because you think it is a
performance
bottleneck,
and hoping that there
aren't any collisions is an
invitation to
disaster.
Chapter
14: Multiple
Threads
853
JavaBeans
revisited
Now
that you understand
synchronization, you can
take another look at
JavaBeans.
Whenever you create a Bean,
you must assume that it
will run
in
a multithreaded environment. This
means that:
1.
Whenever
possible, all the public
methods
of a Bean should be
synchronized.
Of course, this incurs the
synchronized
run-
time
overhead. If that's a problem,
methods that will not
cause
problems
in critical sections can be
left un-synchronized,
but
keep
in mind that this is not
always obvious. Methods that
qualify
tend
to be small (such as getCircleSize(
) in
the following
example)
and/or "atomic," that is,
the method call executes in
such
a
short amount of code that
the object cannot be changed
during
execution.
Making such methods
un-synchronized
might
not
have
a significant effect on the
execution speed of your
program.
You
might as well make all
public
methods
of a Bean
synchronized
and
remove the synchronized
keyword
only
when
you know for sure
that it's necessary and
that it makes a
difference.
2.
When
firing a multicast event to a
bunch of listeners interested
in
that
event, you must assume
that listeners might be
added or
removed
while moving through the
list.
The
first point is fairly easy
to deal with, but the
second point requires
a
little
more thought. Consider the
BangBean.java
example
presented in
the
last chapter. That ducked
out of the multithreading
question by
ignoring
the synchronized
keyword
(which hadn't been
introduced yet)
and
making the event unicast.
Here's that example modified
to work in a
multithreaded
environment and to use
multicasting for
events:
//:
c14:BangBean2.java
//
You should write your Beans this way so they
//
can run in a multithreaded
environment.
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.util.*;
import
java.io.*;
854
Thinking
in Java
import
com.bruceeckel.swing.*;
public
class BangBean2 extends JPanel
implements
Serializable {
private
int xm, ym;
private
int cSize = 20; // Circle size
private
String text = "Bang!";
private
int fontSize = 48;
private
Color tColor = Color.red;
private
ArrayList actionListeners =
new
ArrayList();
public
BangBean2() {
addMouseListener(new
ML());
addMouseMotionListener(new
MM());
}
public
synchronized int getCircleSize() {
return
cSize;
}
public
synchronized void
setCircleSize(int
newSize) {
cSize
= newSize;
}
public
synchronized String getBangText()
{
return
text;
}
public
synchronized void
setBangText(String
newText) {
text
= newText;
}
public
synchronized int getFontSize() {
return
fontSize;
}
public
synchronized void
setFontSize(int
newSize) {
fontSize
= newSize;
}
public
synchronized Color getTextColor()
{
return
tColor;
}
public
synchronized void
setTextColor(Color
newColor) {
Chapter
14: Multiple
Threads
855
tColor
= newColor;
}
public
void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.black);
g.drawOval(xm
- cSize/2, ym - cSize/2,
cSize,
cSize);
}
//
This is a multicast listener, which is
//
more typically used than the unicast
//
approach taken in BangBean.java:
public
synchronized void
addActionListener(ActionListener
l) {
actionListeners.add(l);
}
public
synchronized void
removeActionListener(ActionListener
l) {
actionListeners.remove(l);
}
//
Notice this isn't synchronized:
public
void notifyListeners() {
ActionEvent
a =
new
ActionEvent(BangBean2.this,
ActionEvent.ACTION_PERFORMED,
null);
ArrayList
lv = null;
//
Make a shallow copy of the List in case
//
someone adds a listener while we're
//
calling listeners:
synchronized(this)
{
lv
= (ArrayList)actionListeners.clone();
}
//
Call all the listener methods:
for(int
i = 0; i < lv.size(); i++)
((ActionListener)lv.get(i))
.actionPerformed(a);
}
class
ML extends MouseAdapter {
public
void mousePressed(MouseEvent e) {
Graphics
g = getGraphics();
g.setColor(tColor);
g.setFont(
856
Thinking
in Java
new
Font(
"TimesRoman",
Font.BOLD, fontSize));
int
width =
g.getFontMetrics().stringWidth(text);
g.drawString(text,
(getSize().width
- width) /2,
getSize().height/2);
g.dispose();
notifyListeners();
}
}
class
MM extends MouseMotionAdapter {
public
void mouseMoved(MouseEvent e) {
xm
= e.getX();
ym
= e.getY();
repaint();
}
}
public
static void main(String[] args) {
BangBean2
bb = new BangBean2();
bb.addActionListener(new
ActionListener() {
public
void actionPerformed(ActionEvent e){
System.out.println("ActionEvent"
+ e);
}
});
bb.addActionListener(new
ActionListener() {
public
void actionPerformed(ActionEvent e){
System.out.println("BangBean2
action");
}
});
bb.addActionListener(new
ActionListener() {
public
void actionPerformed(ActionEvent e){
System.out.println("More
action");
}
});
Console.run(bb,
300, 300);
}
}
///:~
Adding
synchronized
to
the methods is an easy
change. However,
notice
in addActionListener(
) and
removeActionListener(
) that
Chapter
14: Multiple
Threads
857
the
ActionListeners
are now added to and
removed from an
ArrayList,
so you can have as many as
you want.
You
can see that the
method notifyListeners(
) is
not
synchronized.
It
can be called from more
than one thread at a time.
It's also possible
for
addActionListener(
) or
removeActionListener(
) to
be called in
the
middle of a call to notifyListeners(
),
which is a problem since
it
traverses
the ArrayList
actionListeners. To alleviate
the problem, the
ArrayList
is
cloned inside a synchronized
clause
and the clone is
traversed
(see Appendix A for details
of cloning). This way the
original
ArrayList
can
be manipulated without impact on
notifyListeners(
).
The
paintComponent( )
method
is also not synchronized.
Deciding
whether
to synchronize overridden methods is
not as clear as when
you're
just
adding your own methods. In
this example it turns out
that paint(
)
seems
to work OK whether it's
synchronized
or
not. But the issues
you
must
consider are:
1.
Does
the method modify the
state of "critical" variables
within the
object?
To discover whether the
variables are "critical" you
must
determine
whether they will be read or
set by other threads in
the
program.
(In this case, the
reading or setting is virtually
always
accomplished
via synchronized
methods,
so you can just
examine
those.) In the case of
paint(
),
no modification takes
place.
2.
Does
the method depend on the
state of these "critical"
variables?
If
a synchronized
method
modifies a variable that
your method
uses,
then you might very
well want to make your
method
synchronized
as
well. Based on this, you
might observe that
cSize
is
changed by synchronized
methods
and therefore
paint(
) should
be synchronized.
Here, however, you can
ask
"What's
the worst thing that
will happen if cSize
is
changed during
a
paint(
)?"
When you see that
it's nothing too bad,
and a
transient
effect at that, you can
decide to leave paint(
) un-
synchronized
to
prevent the extra overhead
from the
synchronized
method
call.
3.
A
third clue is to notice
whether the base-class
version of paint(
)
is
synchronized,
which it isn't. This isn't
an airtight argument,
858
Thinking
in Java
just
a clue. In this case, for
example, a field that
is
changed
via
synchronized
methods
(that is cSize)
has been mixed into
the
paint(
) formula
and might have changed
the situation.
Notice,
however,
that synchronized
doesn't
inherit--that is, if a
method
is
synchronized
in
the base class then it
is
not automatically
synchronized
in
the derived class overridden
version.
The
test code in TestBangBean2
has
been modified from that in
the
previous
chapter to demonstrate the
multicast ability of BangBean2
by
adding
extra listeners.
Blocking
A
thread can be in any one of
four states:
1.
New:
The thread object has
been created but it hasn't
been started
yet
so it cannot run.
2.
Runnable:
This means that a thread
can
be
run when the
time-
slicing
mechanism has CPU cycles
available for the thread.
Thus,
the
thread might or might not be
running, but there's nothing
to
prevent
it from being run if the
scheduler can arrange it;
it's not
dead
or blocked.
3.
Dead:
The normal way for a
thread to die is by returning
from its
run(
) method.
You can also call
stop(
),
but this throws an
exception
that's a subclass of Error
(which
means you aren't
forced
to put the call in a
try block).
Remember that throwing
an
exception
should be a special event
and not part of normal
program
execution;
thus the use of stop( )
is
deprecated in Java 2.
There's
also
a destroy(
) method
(which has never been
implemented)
that
you should never call if
you can avoid it since
it's drastic and
doesn't
release object locks.
4.
Blocked:
The thread could be run
but there's something
that
prevents
it. While a thread is in the
blocked state the scheduler
will
simply
skip over it and not
give it any CPU time.
Until a thread
reenters
the runnable state it won't
perform any
operations.
Chapter
14: Multiple
Threads
859
Becoming
blocked
The
blocked state is the most
interesting one, and is
worth further
examination.
A thread can become blocked
for five reasons:
1.
You've
put the thread to sleep by
calling sleep(milliseconds),
in
which
case it will not be run
for the specified
time.
2.
You've
suspended the execution of
the thread with suspend(
).
It
will
not become runnable again
until the thread gets
the
resume(
) message
(these are deprecated in
Java 2, and will be
examined
further).
3.
You've
suspended the execution of
the thread with wait(
).
It will
not
become runnable again until
the thread gets the
notify( )
or
notifyAll(
) message.
(Yes, this looks just
like number 2, but
there's
a distinct difference that
will be revealed.)
4.
The
thread is waiting for some
I/O to complete.
5.
The
thread is trying to call a
synchronized
method
on another
object,
and that object's lock is
not available.
You
can also call yield( )
(a
method of the Thread
class)
to voluntarily
give
up the CPU so that other
threads can run. However,
the same thing
happens
if the scheduler decides
that your thread has
had enough time
and
jumps to another thread.
That is, nothing prevents
the scheduler
from
moving your thread and
giving time to some other
thread. When a
thread
is blocked, there's some
reason that it cannot
continue running.
The
following example shows all
five ways of becoming
blocked. It all
exists
in a single file called
Blocking.java,
but
it will be examined
here
in
discrete pieces. (You'll
notice the "Continued" and
"Continuing" tags
that
allow the code extraction
tool to piece everything
together.)
Because
this example demonstrates
some deprecated methods, you
will
get
deprecation messages when it is
compiled.
First,
the basic framework:
//:
c14:Blocking.java
//
Demonstrates the various ways a thread
860
Thinking
in Java
//
can be blocked.
//
<applet code=Blocking width=350
height=550>
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.io.*;
import
com.bruceeckel.swing.*;
////////////
The basic framework ///////////
class
Blockable extends Thread {
private
Peeker peeker;
protected
JTextField state = new JTextField(30);
protected
int i;
public
Blockable(Container c) {
c.add(state);
peeker
= new Peeker(this, c);
}
public
synchronized int read() { return i; }
protected
synchronized void update() {
state.setText(getClass().getName()
+
" state: i = " + i);
}
public
void stopPeeker() {
//
peeker.stop(); Deprecated in Java 1.2
peeker.terminate();
// The preferred approach
}
}
class
Peeker extends Thread {
private
Blockable b;
private
int session;
private
JTextField status = new JTextField(30);
private
boolean stop = false;
public
Peeker(Blockable b, Container c) {
c.add(status);
this.b
= b;
start();
}
public
void terminate() { stop = true; }
public
void run() {
Chapter
14: Multiple
Threads
861
while
(!stop) {
status.setText(b.getClass().getName()
+
" Peeker " + (++session)
+
"; value = " + b.read());
try
{
sleep(100);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
///:Continued
The
Blockable
class
is meant to be a base class
for all the classes in
this
example
that demonstrate blocking. A
Blockable
object
contains a
JTextField
called
state
that
is used to display information
about the
object.
The method that displays
this information is update(
).
You can
see
it uses getClass(
).getName( ) to produce
the name of the
class
instead
of just printing it out;
this is because update(
) cannot
know the
exact
name of the class it is
called for, since it will be
a class derived from
Blockable.
The
indicator of change in Blockable
is
an int
i, which
will be
incremented
by the run(
) method
of the derived class.
There's
a thread of class Peeker
that
is started for each
Blockable
object,
and the Peeker's
job is to watch its
associated Blockable
object
to
see changes in i
by
calling read(
) and
reporting them in its
status
JTextField.
This is important: Note that
read( )
and
update( )
are
both
synchronized,
which means they require
that the object lock
be
free.
Sleeping
The
first test in this program
is with sleep(
):
///:Continuing
/////////////
Blocking via sleep() ///////////
class
Sleeper1 extends Blockable {
public
Sleeper1(Container c) { super(c); }
public
synchronized void run() {
862
Thinking
in Java
while(true)
{
i++;
update();
try
{
sleep(1000);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class
Sleeper2 extends Blockable {
public
Sleeper2(Container c) { super(c); }
public
void run() {
while(true)
{
change();
try
{
sleep(1000);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
public
synchronized void change() {
i++;
update();
}
}
///:Continued
In
Sleeper1
the
entire run(
) method
is synchronized.
You'll see that
the
Peeker
associated
with this object will
run along merrily until
you
start
the thread, and then
the Peeker
stops
cold. This is one form
of
blocking:
since Sleeper1.run(
) is
synchronized,
and once the
thread
starts
it's always inside run( ),
the method never gives up
the object lock
and
the Peeker
is
blocked.
Sleeper2
provides
a solution by making run(
) un-synchronized.
Only
the
change( )
method
is synchronized,
which means that while
run(
)
is
in sleep(
),
the Peeker
can
access the synchronized
method
it
Chapter
14: Multiple
Threads
863
needs,
namely read(
).
Here you'll see that
the Peeker
continues
running
when you start the
Sleeper2
thread.
Suspending
and resuming
The
next part of the example
introduces the concept of
suspension. The
Thread
class
has a method suspend(
) to
temporarily stop the
thread
and
resume( )
that
restarts it at the point it
was halted. resume(
) must
be
called by some thread
outside the suspended one,
and in this case
there's
a separate class called
Resumer
that
does just that. Each of
the
classes
demonstrating suspend/resume has an
associated resumer:
///:Continuing
///////////
Blocking via suspend() ///////////
class
SuspendResume extends Blockable {
public
SuspendResume(Container c) {
super(c);
new
Resumer(this);
}
}
class
SuspendResume1 extends SuspendResume
{
public
SuspendResume1(Container c) { super(c);}
public
synchronized void run() {
while(true)
{
i++;
update();
suspend();
// Deprecated in Java 1.2
}
}
}
class
SuspendResume2 extends SuspendResume
{
public
SuspendResume2(Container c) { super(c);}
public
void run() {
while(true)
{
change();
suspend();
// Deprecated in Java 1.2
}
}
public
synchronized void change() {
864
Thinking
in Java
i++;
update();
}
}
class
Resumer extends Thread {
private
SuspendResume sr;
public
Resumer(SuspendResume sr) {
this.sr
= sr;
start();
}
public
void run() {
while(true)
{
try
{
sleep(1000);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
sr.resume();
// Deprecated in Java 1.2
}
}
}
///:Continued
SuspendResume1
also
has a synchronized
run( ) method.
Again,
when
you start this thread
you'll see that its
associated Peeker
gets
blocked
waiting for the lock to
become available, which
never happens.
This
is fixed as before in SuspendResume2,
which does not
synchronize
the
entire run(
) method
but instead uses a
separate
synchronized
change( ) method.
You
should be aware that Java 2
deprecates the use of
suspend(
) and
resume(
),
because suspend(
) holds
the object's lock and is
thus
deadlock-prone.
That is, you can
easily get a number of
locked objects
waiting
on each other, and this
will cause your program to
freeze.
Although
you might see them
used in older programs you
should not use
suspend(
) and
resume(
).
The proper solution is
described later in
this
chapter.
Chapter
14: Multiple
Threads
865
Wait
and notify
In
the first two examples,
it's important to understand
that both sleep(
)
and
suspend(
) do not
release
the lock as they are
called. You must be
aware
of this when working with
locks. On the other hand,
the method
wait(
) does
release
the lock when it is called,
which means that
other
synchronized
methods
in the thread object could
be called during a
wait(
).
In the following two
classes, you'll see that
the run(
) method
is
fully
synchronized
in
both cases, however, the
Peeker
still
has full
access
to the synchronized
methods
during a wait(
).
This is because
wait(
) releases
the lock on the object as it
suspends the method
it's
called
within.
You'll
also see that there
are two forms of wait(
).
The first takes an
argument
in milliseconds that has the
same meaning as in sleep(
):
pause
for this period of time.
The difference is that in
wait(
),
the object
lock
is released and
you
can come out of the
wait( )
because
of a
notify(
) as
well as having the clock
run out.
The
second form takes no
arguments, and means that
the wait(
) will
continue
until a notify(
) comes
along and will not
automatically
terminate
after a time.
One
fairly unique aspect of
wait( )
and
notify( )
is
that both methods
are
part
of the base class Object
and
not part of Thread
as
are sleep(
),
suspend(
),
and resume(
).
Although this seems a bit
strange at first--
to
have something that's
exclusively for threading as
part of the universal
base
class--it's essential because
they manipulate the lock
that's also part
of
every object. As a result,
you can put a wait( )
inside
any
synchronized
method,
regardless of whether there's
any threading
going
on inside that particular
class. In fact, the
only
place
you can call
wait(
) is
within a synchronized
method
or block. If you call
wait( )
or
notify(
) within
a method that's not
synchronized,
the
program will
compile,
but when you run it
you'll get an
IllegalMonitorStateException
with
the somewhat
nonintuitive
message
"current thread not owner."
Note that sleep(
),
suspend(
),
and
resume( )
can
all be called within
non-synchronized
methods
since
they don't manipulate the
lock.
866
Thinking
in Java
You
can call wait(
) or
notify( )
only
for your own lock.
Again, you can
compile
code that tries to use
the wrong lock, but it
will produce the
same
IllegalMonitorStateException
message
as before. You can't fool
with
someone
else's lock, but you
can ask another object to
perform an
operation
that manipulates its own
lock. So one approach is to
create a
synchronized
method
that calls notify(
) for
its own object.
However,
in
Notifier
you'll
see the notify(
) call
inside a synchronized
block:
synchronized(wn2)
{
wn2.notify();
}
where
wn2 is
the object of type WaitNotify2.
This method, which is
not
part
of WaitNotify2,
acquires the lock on the
wn2 object,
at which point
it's
legal for it to call
notify( )
for
wn2 and
you won't get
the
IllegalMonitorStateException.
///:Continuing
///////////
Blocking via wait() ///////////
class
WaitNotify1 extends Blockable {
public
WaitNotify1(Container c) { super(c); }
public
synchronized void run() {
while(true)
{
i++;
update();
try
{
wait(1000);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class
WaitNotify2 extends Blockable {
public
WaitNotify2(Container c) {
super(c);
new
Notifier(this);
}
public
synchronized void run() {
while(true)
{
Chapter
14: Multiple
Threads
867
i++;
update();
try
{
wait();
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
}
}
class
Notifier extends Thread {
private
WaitNotify2 wn2;
public
Notifier(WaitNotify2 wn2) {
this.wn2
= wn2;
start();
}
public
void run() {
while(true)
{
try
{
sleep(2000);
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
synchronized(wn2)
{
wn2.notify();
}
}
}
}
///:Continued
wait(
) is
typically used when you've
gotten to the point where
you're
waiting
for some other condition,
under the control of forces
outside your
thread,
to change and you don't
want to idly wait by inside
the thread. So
wait(
) allows
you to put the thread to
sleep while waiting for
the world to
change,
and only when a notify( )
or
notifyAll( )
occurs
does the thread
wake
up and check for changes.
Thus, it provides a way to
synchronize
between
threads.
868
Thinking
in Java
Blocking
on I/O
If
a stream is waiting for some
I/O activity, it will
automatically block. In
the
following portion of the
example, the two classes
work with generic
Reader
and
Writer
objects,
but in the test framework a
piped stream
will
be set up to allow the two
threads to safely pass data
to each other
(which
is the purpose of piped
streams).
The
Sender
puts
data into the Writer
and
sleeps for a random
amount
of
time. However, Receiver
has
no sleep(
),
suspend(
),
or wait(
).
But
when it does a read(
) it
automatically blocks when
there is no more
data.
///:Continuing
class
Sender extends Blockable { // send
private
Writer out;
public
Sender(Container c, Writer out) {
super(c);
this.out
= out;
}
public
void run() {
while(true)
{
for(char
c = 'A'; c <= 'z'; c++) {
try
{
i++;
out.write(c);
state.setText("Sender
sent: "
+
(char)c);
sleep((int)(3000
* Math.random()));
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
catch(IOException e) {
System.err.println("IO
problem");
}
}
}
}
}
class
Receiver extends Blockable {
private
Reader in;
Chapter
14: Multiple
Threads
869
public
Receiver(Container c, Reader in) {
super(c);
this.in
= in;
}
public
void run() {
try
{
while(true)
{
i++;
// Show peeker it's alive
//
Blocks until characters are there:
state.setText("Receiver
read: "
+
(char)in.read());
}
}
catch(IOException e) {
System.err.println("IO
problem");
}
}
}
///:Continued
Both
classes also put information
into their state
fields
and change i
so
the
Peeker
can
see that the thread is
running.
Testing
The
main applet class is
surprisingly simple because
most of the work
has
been
put into the Blockable
framework.
Basically, an array of
Blockable
objects
is created, and since each
one is a thread, they
perform
their own activities when
you press the "start"
button. There's
also
a button and actionPerformed(
) clause
to stop all of the Peeker
objects,
which provides a demonstration of
the alternative to
the
deprecated
(in Java 2) stop(
) method
of Thread.
To
set up a connection between
the Sender
and
Receiver
objects,
a
PipedWriter
and
PipedReader
are
created. Note that
the
PipedReader
in must
be connected to the PipedWriter
out via
a
constructor
argument. After that,
anything that's placed in
out can
later
be
extracted from in,
as if it passed through a pipe
(hence the name).
The
in
and
out objects
are then passed to the
Receiver
and
Sender
constructors,
respectively, which treat
them as Reader
and
Writer
objects
of any type (that is,
they are upcast).
870
Thinking
in Java
The
array of Blockable
references
b is
not initialized at its point
of
definition
because the piped streams
cannot be set up before
that
definition
takes place (the need
for the try
block
prevents this).
///:Continuing
///////////
Testing Everything ///////////
public
class Blocking extends JApplet {
private
JButton
start
= new JButton("Start"),
stopPeekers
= new JButton("Stop Peekers");
private
boolean started = false;
private
Blockable[] b;
private
PipedWriter out;
private
PipedReader in;
class
StartL implements ActionListener {
public
void actionPerformed(ActionEvent e) {
if(!started)
{
started
= true;
for(int
i = 0; i < b.length; i++)
b[i].start();
}
}
}
class
StopPeekersL implements ActionListener
{
public
void actionPerformed(ActionEvent e) {
//
Demonstration of the preferred
//
alternative to Thread.stop():
for(int
i = 0; i < b.length; i++)
b[i].stopPeeker();
}
}
public
void init() {
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
out
= new PipedWriter();
try
{
in
= new PipedReader(out);
}
catch(IOException e) {
System.err.println("PipedReader
problem");
}
b
= new Blockable[] {
Chapter
14: Multiple
Threads
871
new
Sleeper1(cp),
new
Sleeper2(cp),
new
SuspendResume1(cp),
new
SuspendResume2(cp),
new
WaitNotify1(cp),
new
WaitNotify2(cp),
new
Sender(cp, out),
new
Receiver(cp, in)
};
start.addActionListener(new
StartL());
cp.add(start);
stopPeekers.addActionListener(
new
StopPeekersL());
cp.add(stopPeekers);
}
public
static void main(String[] args) {
Console.run(new
Blocking(), 350, 550);
}
}
///:~
In
init(
),
notice the loop that
moves through the entire
array and adds
the
state
and
peeker.status
text
fields to the page.
When
the Blockable
threads
are initially created, each
one automatically
creates
and starts its own
Peeker.
So you'll see the Peekers
running
before
the Blockable
threads
are started. This is
important, as some of
the
Peekers
will get blocked and
stop when the Blockable
threads
start,
and
it's essential to see this
to understand that particular
aspect of
blocking.
Deadlock
Because
threads can become blocked
and
because
objects can have
synchronized
methods
that prevent threads from
accessing that object
until
the synchronization lock is
released, it's possible for
one thread to
get
stuck waiting for another
thread, which in turn waits
for another
thread,
etc., until the chain
leads back to a thread
waiting on the first
one.
You
get a continuous loop of
threads waiting on each
other and no one
can
move. This is called
deadlock.
The claim is that it doesn't
happen that
often,
but when it happens to you
it's frustrating to
debug.
872
Thinking
in Java
There
is no language support to help
prevent deadlock; it's up to
you to
avoid
it by careful design. These
are not comforting words to
the person
who's
trying to debug a deadlocking
program.
The
deprecation of stop(
), suspend(
),
resume(
), and
destroy(
) in Java
2
One
change that has been
made in Java 2 to reduce the
possibility of
deadlock
is the deprecation of Thread's
stop(
),
suspend(
),
resume(
),
and destroy(
) methods.
The
reason that the stop( )
method
is deprecated is because it
doesn't
release
the locks that the
thread has acquired, and if
the objects are in an
inconsistent
state ("damaged") other
threads can view and
modify them in
that
state. The resulting
problems can be subtle and
difficult to detect.
Instead
of using stop(
),
you should follow the
example in
Blocking.java
and
use a flag to tell the
thread when to terminate
itself
by
exiting its run(
) method.
There
are times when a thread
blocks--such as when it is waiting
for
input--and
it cannot poll a flag as it
does in Blocking.java.
In these
cases,
you still shouldn't use
stop(
),
but instead you can
use the
interrupt(
) method
in Thread
to
break out of the blocked
code:
//:
c14:Interrupt.java
//
The alternative approach to using
//
stop() when a thread is blocked.
//
<applet code=Interrupt width=200
height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
class
Blocked extends Thread {
public
synchronized void run() {
try
{
wait();
// Blocks
}
catch(InterruptedException e) {
System.err.println("Interrupted");
}
Chapter
14: Multiple
Threads
873
System.out.println("Exiting
run()");
}
}
public
class Interrupt extends JApplet {
private
JButton
interrupt
= new JButton("Interrupt");
private
Blocked blocked = new Blocked();
public
void init() {
Container
cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(interrupt);
interrupt.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
System.out.println("Button
pressed");
if(blocked
== null) return;
Thread
remove = blocked;
blocked
= null; // to release it
remove.interrupt();
}
});
blocked.start();
}
public
static void main(String[] args) {
Console.run(new
Interrupt(), 200, 100);
}
}
///:~
The
wait( )
inside
Blocked.run(
) produces
the blocked thread.
When
you
press the button, the
blocked
reference
is set to null
so
the garbage
collector
will clean it up, and
then the object's interrupt( )
method
is
called.
The first time you
press the button you'll
see that the thread
quits,
but
after that there's no thread
to kill so you just see
that the button
has
been
pressed.
The
suspend(
) and
resume( )
methods
turn out to be
inherently
deadlock-prone.
When you call suspend(
),
the target thread stops
but it
still
holds any locks that it
has acquired up to that
point. So no other
thread
can access the locked
resources until the thread
is resumed. Any
874
Thinking
in Java
thread
that wants to resume the
target thread and also
tries to use any of
the
locked resources produces
deadlock. You should not
use suspend(
)
and
resume(
),
but instead put a flag in
your Thread
class
to indicate
whether
the thread should be active
or suspended. If the flag
indicates
that
the thread is suspended, the
thread goes into a wait
using wait(
).
When
the flag indicates that
the thread should be resumed
the thread is
restarted
with notify(
).
An example can be produced by
modifying
Counter2.java.
Although the effect is
similar, you'll notice that
the code
organization
is quite different--anonymous inner
classes are used for
all
of
the listeners and the
Thread
is
an inner class, which
makes
programming
slightly more convenient
since it eliminates some of
the
extra
bookkeeping necessary in Counter2.java:
//:
c14:Suspend.java
//
The alternative approach to using suspend()
//
and resume(), which are deprecated in Java 2.
//
<applet code=Suspend width=300
height=100>
//
</applet>
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class Suspend extends JApplet {
private
JTextField t = new JTextField(10);
private
JButton
suspend
= new JButton("Suspend"),
resume
= new JButton("Resume");
private
Suspendable ss = new
Suspendable();
class
Suspendable extends Thread {
private
int count = 0;
private
boolean suspended = false;
public
Suspendable() { start(); }
public
void fauxSuspend() {
suspended
= true;
}
public
synchronized void fauxResume() {
suspended
= false;
notify();
}
Chapter
14: Multiple
Threads
875
Table of Contents:
|
|||||