![]() |
James Thornton |
| Internet Business Consultant | Call Toll Free: 1 (800) 409-2501 |
| About James | My MySpace | Internet Marketing | Enron Loophole | Lock Bumping | Contact Me |
|---|
Objects provide a way to
divide a program up 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.
Some definitions are useful at this
point. 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 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.
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:
//: Counter1.java
// A non-responsive user interface
package c14;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter1 extends Applet {
private int count = 0;
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
private TextField t = new TextField(10);
private boolean runFlag = true;
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
public void go() {
while (true) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e){}
if(runFlag)
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) {
Counter1 applet = new Counter1();
Frame aFrame = new Frame("Counter1");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
At this point, the AWT 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 TextField 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.
currentThread() (a static method of the Thread
class) and then call sleep( ) for that thread.
Note that sleep( ) can
throw 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. And upon 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 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.)
The basic problem here is that
go( ) needs to continue performing its operations, and at the same
time it needs to return so 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 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.
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.
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 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).
//: SimpleThread.java
// Very simple Threading example
public class SimpleThread extends Thread {
private int countDown = 5;
private int threadNumber;
private static int threadCount = 0;
public SimpleThread() {
threadNumber = ++threadCount;
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 call to stop( ) or
destroy( ) for that thread, it will run forever (until the program
completes).
In main( ) you can see
a number of threads being created and run. The special method that comes with
the Thread class is start( ), which
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 every time) 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)
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 handles 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.
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:
//: Counter2.java
// A responsive user interface with threads
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class SeparateSubTask extends Thread {
private int count = 0;
private Counter2 c2;
private boolean runFlag = true;
public SeparateSubTask(Counter2 c2) {
this.c2 = c2;
start();
}
public void invertFlag() { runFlag = !runFlag;}
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e){}
if(runFlag)
c2.t.setText(Integer.toString(count++));
}
}
}
public class Counter2 extends Applet {
TextField t = new TextField(10);
private SeparateSubTask sp = null;
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp == null)
sp = new SeparateSubTask(Counter2.this);
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp != null)
sp.invertFlag();
}
}
public static void main(String[] args) {
Counter2 applet = new Counter2();
Frame aFrame = new Frame("Counter2");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
Counter2 is now a
straightforward program, whose job is only to set up and maintain the user
interface. But now, when the user presses the start button, a method is
not called. Instead a thread of class SeparateSubTask is created (the
constructor starts it, in this case), and then the Counter2 event loop
continues. Note that the handle to the SeparateSubTask is stored so that
when you press the onOff button it can toggle the runFlag inside
the SeparateSubTask object. That thread (when it looks at the flag) can
then start and stop itself. (This could also have been accomplished by making
SeparateSubTask an inner class.)
The class SeparateSubTask is
a simple extension of Thread with a constructor (that stores the
Counter2 handle and then runs the thread by calling start( ))
and a run( ) that essentially contains the code from inside
go( ) in Counter1.java. Because SeparateSubTask knows
that it holds a handle to a Counter2, it can reach in and access
Counter2’s TextField when it needs to.
When you press the onOff
button, you’ll see a virtually 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.
As an aside, look at the coupling
that occurs between the SeparateSubTask and Counter2 classes. The
SeparateSubTask is intimately tied to Counter2 – it must
keep a handle to its “parent” Counter2 object so it can call
back and manipulate it. And yet the two classes shouldn’t really merge
together into a single class (although in the next section you’ll see that
Java provides a way to combine them) because they’re doing separate things
and are created at different times. They are tightly connected (what I call a
“couplet”) and this makes the coding
awkward. This is a situation in which an
inner
class can improve the code significantly:
//: Counter2i.java
// Counter2 using an inner class for the thread
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter2i extends Applet {
private class SeparateSubTask extends Thread {
int count = 0;
boolean runFlag = true;
SeparateSubTask() { start(); }
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e){}
if(runFlag)
t.setText(Integer.toString(count++));
}
}
}
private SeparateSubTask sp = null;
private TextField t = new TextField(10);
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
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.runFlag = !sp.runFlag; // invertFlag();
}
}
public static void main(String[] args) {
Counter2i applet = new Counter2i();
Frame aFrame = new Frame("Counter2i");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
This SeparateSubTask name
will not collide with the SeparateSubTask in the previous example even
though they’re in the same directory, since it’s hidden as an inner
class. You can also see that the
inner class 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
Counter2i, and since the two classes are tightly coupled it’s
convenient to loosen the access restrictions between them. In
SeparateSubTask you can see that the invertFlag( ) method has
been removed since Counter2i can now directly access
runFlag.
Also, notice that
SeparateSubTask’s constructor has been simplified – now it
only starts the thread. The handle to the Counter2i object is still being
captured as in the previous version, but instead of doing it by hand and
referencing the outer object by hand, the inner class mechanism takes care of it
automatically. In run( ), you can see that t is simply
accessed, as if it were a field of SeparateSubTask. The t field in
the parent class can now be made 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.
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.
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:
//: Counter3.java
// Using the Runnable interface to turn the
// main class into a thread.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Counter3
extends Applet implements Runnable {
private int count = 0;
private boolean runFlag = true;
private Thread selfThread = null;
private Button
onOff = new Button("Toggle"),
start = new Button("Start");
private TextField t = new TextField(10);
public void init() {
add(t);
start.addActionListener(new StartL());
add(start);
onOff.addActionListener(new OnOffL());
add(onOff);
}
public void run() {
while (true) {
try {
selfThread.sleep(100);
} catch (InterruptedException e){}
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 static void main(String[] args) {
Counter3 applet = new Counter3();
Frame aFrame = new Frame("Counter3");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
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:
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 thread
separately and hand it the Runnable object; there’s a special
constructor for this that takes a Runnable as its argument. 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. The penalty for this convenience is strict, though – you can have
only a single thread running for that particular object (although you can create
more objects of that type, or create other threads in different
classes).
Note that the Runnable
interface is not what imposes this restriction. It’s the combination of
Runnable and your main class that does it, since you can have only one
object of your main class per
application.
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 information. When a Ticker object is
created, the constructor requires a handle to an AWT Container, which
Ticker fills with its visual components. This way, if you change the
visual components, the code that uses Ticker doesn’t need to be
modified.
//: Counter4.java
// If you separate your thread from the main
// class, you can have as many threads as you
// want.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
class Ticker extends Thread {
private Button b = new Button("Toggle");
private TextField t = new TextField(10);
private int count = 0;
private boolean runFlag = true;
public Ticker(Container c) {
b.addActionListener(new ToggleL());
Panel p = new Panel();
p.add(t);
p.add(b);
c.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++));
try {
sleep(100);
} catch (InterruptedException e){}
}
}
}
public class Counter4 extends Applet {
private Button start = new Button("Start");
private boolean started = false;
private Ticker[] s;
private boolean isApplet = true;
private int size;
public void init() {
// Get parameter "size" from Web page:
if(isApplet)
size =
Integer.parseInt(getParameter("size"));
s = new Ticker[size];
for(int i = 0; i < s.length; i++)
s[i] = new Ticker(this);
start.addActionListener(new StartL());
add(start);
}
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 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;
applet.size =
(args.length == 0 ? 5 :
Integer.parseInt(args[0]));
Frame aFrame = new Frame("Counter4");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(200, applet.size * 50);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~
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 description:
<applet code=Counter4 width=600 height=600> <param name=size value="20"> </applet>
The
param,
name, and
value are all Web-page
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.
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.
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:
//: 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() {
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) {
Thread d = new Daemon();
System.out.println(
"d.isDaemon() = " + d.isDaemon());
// Allow the daemon threads to finish
// their startup processes:
BufferedReader stdin =
new BufferedReader(
new InputStreamReader(System.in));
System.out.println("Waiting for CR");
try {
stdin.readLine();
} catch(IOException e) {}
}
} ///:~
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 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 carriage return before terminating. Without this you see
only some of the results from the creation of the daemon threads. (Try replacing
the readLine( ) code with sleep( ) calls of various
lengths to see this
behavior.)
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.
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.
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.
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.
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.
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:
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:
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.
The blocked state is the most
interesting and is worth further examination. A thread can become blocked for
five reasons:
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 re-starting your 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 tool shown in Chapter 17 to piece everything together.)
First, the basic framework:
//: Blocking.java
// Demonstrates the various ways a thread
// can be blocked.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.io.*;
//////////// The basic framework ///////////
class Blockable extends Thread {
private Peeker peeker;
protected TextField state = new TextField(40);
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 TextField status = new TextField(40);
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() {
while (!stop) {
status.setText(b.getClass().getName()
+ " Peeker " + (++session)
+ "; value = " + b.read());
try {
sleep(100);
} catch (InterruptedException e){}
}
}
} ///: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 TextField 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 TextField. This is important: Note that read( ) and
update( ) are both synchronized, which means they require
that the object lock be free.
///:Continuing
///////////// Blocking via sleep() ///////////
class Sleeper1 extends Blockable {
public Sleeper1(Container c) { super(c); }
public synchronized void run() {
while(true) {
i++;
update();
try {
sleep(1000);
} catch (InterruptedException e){}
}
}
}
class Sleeper2 extends Blockable {
public Sleeper2(Container c) { super(c); }
public void run() {
while(true) {
change();
try {
sleep(1000);
} catch (InterruptedException e){}
}
}
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 needs, namely read( ). Here you’ll see that the
Peeker continues running when you start the Sleeper2
thread.
The next part of the example
introduces the concept of suspension. The Thread class has a method
suspend( ) to
temporarily halt the thread and
resume( ) that
re-starts it at the point it was halted. Presumably, resume( ) is
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() {
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){}
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
1.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.
The point with the first two
examples is 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 non-intuitive 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.
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){}
}
}
}
class WaitNotify2 extends Blockable {
public WaitNotify2(Container c) {
super(c);
new Notifier(this);
}
public synchronized void run() {
while(true) {
i++;
update();
try {
wait();
} catch (InterruptedException e){}
}
}
}
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){}
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.
If a stream is waiting for some IO
activity, it will automatically block. In the following portion of the example,
the two classes work with generic Reader and
Writer objects (using the Java
1.1 Streams), 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){}
catch (IOException e) {}
}
}
}
}
class Receiver extends Blockable {
private Reader in;
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) { e.printStackTrace();}
}
} ///:Continued
Both classes also put information
into their state fields and change i so the Peeker can see
that the thread is running.
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
1.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).
The array of Blockable
handles 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 Applet {
private Button
start = new Button("Start"),
stopPeekers = new Button("Stop Peekers");
private boolean started = false;
private Blockable[] b;
private PipedWriter out;
private PipedReader in;
public void init() {
out = new PipedWriter();
try {
in = new PipedReader(out);
} catch(IOException e) {}
b = new Blockable[] {
new Sleeper1(this),
new Sleeper2(this),
new SuspendResume1(this),
new SuspendResume2(this),
new WaitNotify1(this),
new WaitNotify2(this),
new Sender(this, out),
new Receiver(this, in)
};
start.addActionListener(new StartL());
add(start);
stopPeekers.addActionListener(
new StopPeekersL());
add(stopPeekers);
}
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 static void main(String[] args) {
Blocking applet = new Blocking();
Frame aFrame = new Frame("Blocking");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(350,550);
applet.init();
applet.start();
aFrame.setVisible(tru