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:
//: Sharing1.java
// Problems with resource sharing while threading
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class TwoCounter extends Thread {
private boolean started = false;
private TextField
t1 = new TextField(5),
t2 = new TextField(5);
private Label l =
new Label("count1 == count2");
private int count1 = 0, count2 = 0;
// Add the display components as a panel
// to the given container:
public TwoCounter(Container c) {
Panel p = new Panel();
p.add(t1);
p.add(t2);
p.add(l);
c.add(p);
}
public void start() {
if(!started) {
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){}
}
}
public void synchTest() {
Sharing1.incrementAccess();
if(count1 != count2)
l.setText("Unsynched");
}
}
class Watcher extends Thread {
private Sharing1 p;
public Watcher(Sharing1 p) {
this.p = p;
start();
}
public void run() {
while(true) {
for(int i = 0; i < p.s.length; i++)
p.s[i].synchTest();
try {
sleep(500);
} catch (InterruptedException e){}
}
}
}
public class Sharing1 extends Applet {
TwoCounter[] s;
private static int accessCount = 0;
private static TextField aCount =
new TextField("0", 10);
public static void incrementAccess() {
accessCount++;
aCount.setText(Integer.toString(accessCount));
}
private Button
start = new Button("Start"),
observer = new Button("Observe");
private boolean isApplet = true;
private int numCounters = 0;
private int numObservers = 0;
public void init() {
if(isApplet) {
numCounters =
Integer.parseInt(getParameter("size"));
numObservers =
Integer.parseInt(
getParameter("observers"));
}
s = new TwoCounter[numCounters];
for(int i = 0; i < s.length; i++)
s[i] = new TwoCounter(this);
Panel p = new Panel();
start.addActionListener(new StartL());
p.add(start);
observer.addActionListener(new ObserverL());
p.add(observer);
p.add(new Label("Access Count"));
p.add(aCount);
add(p);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < s.length; i++)
s[i].start();
}
}
class ObserverL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < numObservers; i++)
new Watcher(Sharing1.this);
}
}
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 ? 5 :
Integer.parseInt(args[0]));
applet.numObservers =
(args.length < 2 ? 5 :
Integer.parseInt(args[1]));
Frame aFrame = new Frame("Sharing1");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(350, applet.numCounters *100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
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
Container
in
the
TwoCounter
constructor. Because this thread is started via a button press 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
that 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
future 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 “Observe” button, one or more observers are
created and freed upon the unsuspecting
TwoCounter
threads. Note
that to run this as an applet in a browser, your Web page will need to contain
the lines:
<applet code=Sharing1 width=650 height=500>
<param name=size value="20">
<param name=observers value="1">
</applet>
You
can change the width, height, and parameters to suit your experimental tastes.
By changing the
size
and
observers
you’ll change the behavior of the program. You can also see that 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
Watcher)
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.
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
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:
//: Sharing2.java
// Using the synchronized keyword to prevent
// multiple access to a particular resource.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class TwoCounter2 extends Thread {
private boolean started = false;
private TextField
t1 = new TextField(5),
t2 = new TextField(5);
private Label l =
new Label("count1 == count2");
private int count1 = 0, count2 = 0;
public TwoCounter2(Container c) {
Panel p = new Panel();
p.add(t1);
p.add(t2);
p.add(l);
c.add(p);
}
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){}
}
}
public synchronized void synchTest() {
Sharing2.incrementAccess();
if(count1 != count2)
l.setText("Unsynched");
}
}
class Watcher2 extends Thread {
private Sharing2 p;
public Watcher2(Sharing2 p) {
this.p = p;
start();
}
public void run() {
while(true) {
for(int i = 0; i < p.s.length; i++)
p.s[i].synchTest();
try {
sleep(500);
} catch (InterruptedException e){}
}
}
}
public class Sharing2 extends Applet {
TwoCounter2[] s;
private static int accessCount = 0;
private static TextField aCount =
new TextField("0", 10);
public static void incrementAccess() {
accessCount++;
aCount.setText(Integer.toString(accessCount));
}
private Button
start = new Button("Start"),
observer = new Button("Observe");
private boolean isApplet = true;
private int numCounters = 0;
private int numObservers = 0;
public void init() {
if(isApplet) {
numCounters =
Integer.parseInt(getParameter("size"));
numObservers =
Integer.parseInt(
getParameter("observers"));
}
s = new TwoCounter2[numCounters];
for(int i = 0; i < s.length; i++)
s[i] = new TwoCounter2(this);
Panel p = new Panel();
start.addActionListener(new StartL());
p.add(start);
observer.addActionListener(new ObserverL());
p.add(observer);
p.add(new Label("Access Count"));
p.add(aCount);
add(p);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < s.length; i++)
s[i].start();
}
}
class ObserverL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < numObservers; i++)
new Watcher2(Sharing2.this);
}
}
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 ? 5 :
Integer.parseInt(args[0]));
applet.numObservers =
(args.length < 2 ? 5 :
Integer.parseInt(args[1]));
Frame aFrame = new Frame("Sharing2");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(350, applet.numCounters *100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
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
Watcher2
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, assuming all
// threads respect syncObject's lock
}
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:
public void run() {
while (true) {
synchronized(this) {
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
}
try {
sleep(500);
} catch (InterruptedException e){}
}
}
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 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.
Java
Beans revisited
Now
that you understand synchronization you can take another look at Java
Beans. Whenever you create a Bean, you must assume that it will run in a
multithreaded environment. This means that:
- Whenever
possible, all the public methods of a Bean should be
synchronized.
Of course, this incurs the
synchronized
runtime 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.
- 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:
//: BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
public class BangBean2 extends Canvas
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 Vector actionListeners = new Vector();
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) {
tColor = newColor;
}
public void paint(Graphics 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.addElement(l);
}
public synchronized void removeActionListener(
ActionListener l) {
actionListeners.removeElement(l);
}
// Notice this isn't synchronized:
public void notifyListeners() {
ActionEvent a =
new ActionEvent(BangBean2.this,
ActionEvent.ACTION_PERFORMED, null);
Vector lv = null;
// Make a copy of the vector in case someone
// adds a listener while we're
// calling listeners:
synchronized(this) {
lv = (Vector)actionListeners.clone();
}
// Call all the listener methods:
for(int i = 0; i < lv.size(); i++) {
ActionListener al =
(ActionListener)lv.elementAt(i);
al.actionPerformed(a);
}
}
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();
notifyListeners();
}
}
class MM extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
xm = e.getX();
ym = e.getY();
repaint();
}
}
// Testing the BangBean2:
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");
}
});
Frame aFrame = new Frame("BangBean2 Test");
aFrame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(bb, BorderLayout.CENTER);
aFrame.setSize(300,300);
aFrame.setVisible(true);
}
} ///:~
Adding
synchronized
to the methods is an easy change. However, notice in addActionListener( )
and removeActionListener( )
that the
ActionListeners
are now added to and removed from a
Vector,
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
Vector
actionListeners
.
To alleviate the problem, the
Vector
is cloned inside a
synchronized
clause and the clone is traversed. This way the original
Vector
can be manipulated without impact on
notifyListeners( ). The
paint( )
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:
- 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.
- 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,
it’s best to leave
paint( )
un-
synchronized
to prevent the extra overhead from the
synchronized
method call.
- 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, 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.