The
wait()
method—like the
sleep() and
join() methods that we examined in Section 2.1—may under certain circumstances throw an
InterruptedException. These methods all throw such an exception when
the thread in which they are executing is interrupted, which occurs
when another thread calls this method:
Sends an interruption to the specified thread. If the thread is
currently blocked in a thread-related method (i.e., the
sleep(),
join(), or
wait() methods), the thread moves to an
unblocked state; otherwise, a boolean flag is simply set to indicate
that the thread has been interrupted.
The effect of the
interrupt() method depends on
whether the target of the interruption is executing a method that
might throw an exception based on that interruption. When the thread
is executing the
sleep(),
wait(), and
join() methods,
those methods will throw an InterruptedException. Otherwise, a flag
is set that the thread can examine to determine that the
interrupt() method has been called.[4]
The
interrupt() method is a method of the Thread
class, and it is used by one thread to signal another thread: it is
possible (although it doesn’t really make sense) for a thread
to interrupt itself. The target thread knows it has been interrupted
if it is executing a method that will throw an
InterruptedException. Otherwise, the
target thread must use one of these methods to check if it has been
interrupted:
Returns a boolean that indicates whether the current thread has been
interrupted. This is a static method of the Thread class and may be
called through the class specifier. This method simply returns a flag
that is set by the
interrupt() method.
Returns a boolean that indicates whether the specified thread has
been interrupted. This method simply returns a flag that is set by
the
interrupt() method. and must be executed on a thread object.
The point behind these methods is that the internal implementation of
the
interrupt() method sets a value somewhere in
the target thread object that indicates that the
interrupt() method has been called; these
methods simply return that flag.
You can use the
interrupt() method to signal a
different outcome to a waiting thread. One of the common uses of the
wait and notify mechanism is in a producer-consumer scenario: one or
more threads are responsible for producing data (for example, by
reading it from some server), and one or more threads are responsible
for consuming data (for example, by parsing the data). When there is
no data to consume, consumer threads spend a great deal of time
waiting:
import java.util.*; public class Consumer extends Thread { Vector data; public Consumer(Vector data) { this.data = data; } public void run() { Object o; while (true) { synchronized(data) { if (isInterrupted()) return; while (data.size() == 0) { try { data.wait(); } catch (InterruptedException ie) { return; } } o = data.elementAt(0); data.removeElementAt(0); } process(o); } } }
Rather than stopping a consumer thread by setting a flag that its
run() method consults, we now rely on another
thread to interrupt it. Note that there are two possible outcomes
here: if the interrupt arrives while the consumer is executing the
wait() method, an exception is thrown and the
run() method returns. However, if the interrupt
arrives while the consumer is executing the
process() method, then the
process() method will complete, and the consumer
will exit the
run() method when it next checks
the interrupted flag. In order to prevent the interrupt from arriving
at any other time, we must only send the interrupt from a thread that
has synchronized on the data object that was passed into the
constructor.
Be aware that the code internal to the Java virtual machine will not
set the interrupted flag if the thread that is being interrupted is
executing the
sleep(),
wait(), or
join() methods.
Consider the following code:
boolean done = false; synchronized (lock) { while (!done) { try { lock.wait(); } catch (InterruptedException ie) { done = isInterrupted(); } } }
In the
catch clause, the variable
done will be set to
false and
the loop will never exit.
In some circumstances, this may make the
interrupt() method less useful in stopping a
thread than directly setting a boolean flag in the target thread.
However, because it will interrupt the
sleep()
and
wait() methods, and because of some thread
management techniques that we’ll learn about in Chapter 10, the
interrupt() method
can be very useful in cases such as this.
One
area of confusion that surrounds the
interrupt()
method is in the area of I/O: can the
interrupt() method affect a thread that is
blocked while waiting for I/O? The answer for the time being is that
it cannot, and you should not rely on its ability to do so. This may
change in future releases of the virtual machine.
However, as it turns out, there are some implementations[5] of the
virtual machine—most notably, the Solaris native-thread
implementation—that cause the
interrupt()
method to interrupt any pending I/O. Hence, if a thread that is
blocked on the
read() method is the target of
the
interrupt() method, the
read() method will throw an IOException. The
particular IOException that is thrown varies: in Java 2, an
InterruptedIOException is thrown; in
1.1, other exceptions (e.g., SocketException) are thrown. This may
also change in future releases of the virtual machine: in the future,
Solaris native-thread implementations may not allow I/O to be
interrupted. In some green-thread versions of the virtual machine,
some I/O methods will throw an InterruptedIOException, and some I/O
methods will not. Interruptible I/O is not really possible on
Windows, so Windows virtual machines do not support it.
So, what’s a programmer to do? The safest
answer is not to rely on the
interrupt() method
to unblock a thread that is waiting for I/O to complete: if you need
to unblock such a thread, you should close the input or output stream
on which the thread is blocked. If interruptible I/O is a generic
feature added to Java virtual machines in the future, it will likely
have a different interface. If you do rely on interruptible I/O, be
aware that the I/O in question is not restartable: it’s
impossible to determine the state of the I/O and know at which point
it should start again. The difficulty of dealing with the issue of
restarting the I/O that has been interrupted is a prime reason why
its implementation is inconsistent between virtual machines.
What if we want to use the interrupt() method more
generically—that is, to get a thread to terminate, regardless
of whether it’s blocked on I/O or not? In our last
example, we were able to take advantage of the fact that the
wait() method had thrown an exception to know
that there was no more data coming. If you want to do the same thing
with I/O, you can do something like this:
import java.util.*; import java.io.*; import java.net.*; class StockObservable extends Observable { String lastTick; void setTick(String s) { lastTick = s; setChanged(); notifyObservers(); } } public class StockHandler extends Thread { private BufferedReader br; private InputStream is; private Socket sock; private StockObservable stock; private volatile boolean done = false; private Object lock = new Object(); class StockHandlerThread extends Thread { public void run() { String s; try { while ((s = br.readLine()) != null) stock.setTick(s); } catch (IOException ioe) {} done = true; synchronized(lock) { lock.notify(); } } } public StockHandler(StockObservable o, String host, int port) throws IOException, UnknownHostException { sock = new Socket(host, port); is = sock.getInputStream(); stock = o; } public void run() { br = new BufferedReader(new InputStreamReader(is)); Thread t = new StockHandlerThread(); t.start(); synchronized(lock) { while (!done) { try { lock.wait(Integer.MAX_VALUE); } catch (InterruptedException ie) { done = true; try { t.interrupt(); is.close(); sock.close(); } catch (IOException ioe) {} } } } } }
We’ve often mentioned that starting a separate thread to handle
I/O is one of the more common uses of threads in Java; here’s
an example of that technique. This class sets up a socket to the
stock server, reads data from that server, and publishes that data
via the given observable object. The
read()
method in such a case would often block, and when it comes time to
stop the thread, we have to have some way to get the
read() method to terminate. This is accomplished
by closing the socket from which the thread is reading.
However, what we’ve done in this example is to start two threads: one thread that is reading the data, and one thread that is waiting for an interrupt to occur (since the timeout is unlikely to occur). When the waiting thread is interrupted, it closes the input stream that the reading thread is blocked on, and both threads will then exit. This allows us to shut down the thread (and the socket associated with the thread) by interrupting the waiting thread:
Thread t = new StockHandler(...); ... Do other stuff until we need to shut down the handler ... t.interrupt();
Now, clearly we could simply have exposed the socket and input stream
instance variables so that any thread could have closed them
directly. We’d rarely choose to do that, however, since
it’s better to encapsulate knowledge like that in the class to
which it belongs. Similarly, we could have provided another method
(e.g., a
shutdown() method) that closes the
socket and input stream. That sort of interface would have saved us a
thread: the StockHandler class would read the data in its
run() method and an external thread could
execute its
shutdown()
method.
You can make an argument for and against including such a method in
the interface for the StockHandler; we’ll just mention again in
passing that some of the thread management techniques that
we’ll look at in Chapter 10 make the
interrupt() method a useful choice.
Finally, note that before we closed the input stream in order to get
the stock handler thread
t to unblock, we also
called the
interrupt() method on
t. The primary reason for that is a bug: in
Solaris 2.6 and earlier releases with a native-thread implementation
of the virtual machine, the
close() method in
our example will block until the
read() method
that is being executed in the other thread also blocks. Although this
bug is fixed in Solaris 2.7, it doesn’t hurt to call the
interrupt() method in any release so that our
example will work on earlier Solaris releases, as well as on
green-thread or Windows releases. More generally, the stock handler
thread might be executing a
wait() or
sleep() method, in which case it would also be
necessary
to
interrupt it.
No credit card required