ZeePedia

Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited

<< Creating Windows & Applets:Applet restrictions, Running applets from the command line
Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans >>
img
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
img
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
img
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
img
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
img
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
img
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
img
// ...
}
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
img
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
img
}
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
img
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
img
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
img
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
img
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
img
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
img
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
img
}
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
img
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
img
// 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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
}
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
img
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
img
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
img
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
img
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
img
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
img
} ///:~
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
img
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
img
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
img
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
img
// <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
img
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
img
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
img
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
img
<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
img
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
img
// 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
img
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
img
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
img
(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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
// 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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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:
  1. Introduction to Objects:The progress of abstraction, An object has an interface
  2. Everything is an Object:You manipulate objects with references, Your first Java program
  3. Controlling Program Flow:Using Java operators, Execution control, true and false
  4. Initialization & Cleanup:Method overloading, Member initialization
  5. Hiding the Implementation:the library unit, Java access specifiers, Interface and implementation
  6. Reusing Classes:Composition syntax, Combining composition and inheritance
  7. Polymorphism:Upcasting revisited, The twist, Designing with inheritance
  8. Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers
  9. Holding Your Objects:Container disadvantage, List functionality, Map functionality
  10. Error Handling with Exceptions:Basic exceptions, Catching an exception
  11. The Java I/O System:The File class, Compression, Object serialization, Tokenizing input
  12. Run-time Type Identification:The need for RTTI, A class method extractor
  13. Creating Windows & Applets:Applet restrictions, Running applets from the command line
  14. Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited
  15. Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans
  16. A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
  17. B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument
  18. Java Programming Guidelines:Design, Implementation
  19. Resources:Software, Books, My own list of books
  20. Index