Blocking
Blocking
A thread can be in any one of four states:
- New: The thread object has been created but it
hasn’t been started yet so it cannot run.
- 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.
- 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.
- 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.
Becoming blocked
The blocked state is the most interesting one, and is
worth further examination. A thread can become blocked for five reasons:
- You’ve put the thread to sleep by calling
sleep(milliseconds), in which case it will not be run for the specified
time.
- 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). 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.)
- The thread is waiting for some I/O to complete.
- The thread is trying to call a synchronized
method on another objet, 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
//
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() {
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() {
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 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() {
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.
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.
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)
{
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.
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;
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).
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[] {
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.
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");
}
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 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();
}
public
void
run() {
while
(true)
{
try
{
sleep(100);
synchronized(this)
{
while(suspended)
wait();
}
}
catch(InterruptedException
e) {
System.err.println("Interrupted");
}
t.setText(Integer.toString(count++));
}
}
}
public
void
init() {
Container cp = getContentPane();
cp.setLayout(new
FlowLayout());
cp.add(t);
suspend.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
ss.fauxSuspend();
}
});
cp.add(suspend);
resume.addActionListener(
new
ActionListener() {
public
void
actionPerformed(ActionEvent e) {
ss.fauxResume();
}
});
cp.add(resume);
}
public
static
void
main(String[] args) {
Console.run(new
Suspend(), 300, 100);
}
}
///:~
The flag suspended inside Suspendable is
used to turn suspension on and off. To suspend, the flag is set to true
by calling fauxSuspend( ) and this is detected inside
run( ). The wait( ), as described earlier in this
chapter, must be synchronized so that it has the object lock. In
fauxResume( ), the suspended flag is set to false and
notify( ) is called—since this wakes up wait( )
inside a synchronized clause the fauxResume( ) method must
also be synchronized so that it acquires the lock before calling
notify( ) (thus the lock is available for the wait( ) to
wake up with). If you follow the style shown in this program you can avoid using
suspend( ) and resume( ).
The destroy( ) method of Thread has
never been implemented; it’s like a suspend( ) that cannot
resume, so it has the same deadlock issues as suspend( ). However,
this is not a deprecated method and it might be implemented in a future version
of Java (after 2) for special situations in which the risk of a deadlock is
acceptable.
You might wonder why these methods, now deprecated,
were included in Java in the first place. It seems an admission of a rather
significant mistake to simply remove them outright (and pokes yet another hole
in the arguments for Java’s exceptional design and infallibility touted by
Sun marketing people). The heartening part about the change is that it clearly
indicates that the technical people and not the marketing people are running the
show—they discovered a problem and they are fixing it. I find this much
more promising and hopeful than leaving the problem in because “fixing it
would admit an error.” It means that Java will continue to improve, even
if it means a little discomfort on the part of Java programmers. I’d
rather deal with the discomfort than watch the language
stagnate
|