Thread Interruption
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:
void interrupt() ( Java 1.1 and above only)
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.
Thread Interruption in Java 1.0
If you happen to have Java 1.0, you’ll find that the various interrupt-related methods that we’re describing in this section do not function: they all simply throw a NoSuchMethodError.
In Java 1.0.2—and browsers such as Netscape 3.0 and Internet Explorer 3.0 that are based on that release—these methods do not work properly. In particular, the interrupt()
method is not able to interrupt a thread that is sleeping. While you can use the isInterrupted()
method to determine if the interrupt()
method has been called, the 1.0.2 implementation of these methods does not help you to deal with blocked threads.
Thread interruption works in Java 1.1 and later releases, but it does not work in many 1.1-based browsers. For now, we recommend that you use these methods only in Java applications or in applets run with the Java plug-in.
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:
static boolean interrupted() ( Java 1.1 and above only)
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.
boolean isInterrupted() ( Java 1.1 and above only)
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.
The main difference between these two methods is that the interrupted()
method resets the value of the flag to false. The isInterrupted()
method does not change the value of the flag. Note that the interrupted()
method is static and operates on the current thread, whereas the isInterrupted()
method is dynamic and 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.
Interrupted I/O
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.
[4] In some virtual machines, there’s an additional possibility that we’ll examine a little later.
[5] These various implementations of the virtual machine are discussed in Chapter 6.
https://www.oreilly.com/library/view/java-threads-second/1565924185/ch04s05.html