Blocking for I/O
A running thread, on executing a blocking operation requiring a resource (like a call to an I/O method), will transit to the BLOCKED state (Figure 22.12). The getState() method on this thread will return the value Thread.State.BLOCKED. When the thread can complete the blocking operation, it proceeds to the READY substate. An example is a thread reading from the standard input terminal, that blocks until input is provided:
int input = System.in.read();
Figure 22.12 Blocked for I/O
Thread Termination
A thread can transit to the TERMINATED state from the RUNNABLE state (Figure 22.13). The thread terminates when it finishes executing its run() method, either by returning normally or by throwing an exception. Once in this state, the thread cannot be resurrected. There is no way the thread can be enabled for running again, not even by calling the start() method again on the thread object. The getState() method on this thread will return the value Thread.State.TERMINATED.
Figure 22.13 Thread Termination
The main thread is special in that it is started and terminated when the main() method starts and stops execution, respectively.
It might be tempting to call the System.exit() method to terminate a thread, but this can be rather drastic. A call to this method terminates the process in which the JVM is running, and as a consequence, terminating all threads that might still be alive in the JVM.
Controlling a Thread
Example 22.8 illustrates a typical scenario where a thread can be controlled by one or more threads. Work is performed by a loop body, which the thread executes continuously. It should be possible for other threads to start and stop the worker thread. This functionality is implemented by the class Worker at (1), which has a private field theThread declared at (2) to keep track of the Thread object executing its run() method at (5).
The kickStart() method at (3) in the class Worker creates and starts a thread if the field theThread is not already denoting a running thread—that is, if the field has the null value. The terminate() method at (4) sets the field theThread to null to signal that the run() method can terminate, thus resulting in the thread being terminated. Note that this does not affect any Thread object that might have been referenced by the field theThread. The runtime system maintains any such Thread object; therefore, changing one of its references does not affect the Thread object.
The run() method at (5) has a loop whose execution is controlled by a special condition. The condition tests to see whether the Thread object referenced by the reference theThread and the Thread object currently running are one and the same. This is bound to be the case if the reference theThread has the same reference value that it was assigned when the thread was created and started in the kickStart() method. The condition will then be true, and the body of the loop will execute. However, if the value in the reference theThread has changed, the condition will be false. In that case, the loop will not execute, the run() method will complete, and the thread will terminate. This idiom is generally recommended to terminate a thread.
A client can control the thread implemented by the class Worker, using the kick-Start() and the terminate() methods. The client is able to terminate the running thread at the start of the next iteration of the loop body by calling the terminate() method that changes the value of the theThread reference to null.
In Example 22.8, a Worker object is first created at (8) and a thread started on this Worker object at (9). The main thread invokes the sleep() method at (10) to temporarily cease its execution for 2 milliseconds, giving the thread of the Worker object a chance to run. The main thread, when it is executing again, terminates the thread of the Worker object at (11), as explained earlier. This simple scenario can be generalized where several threads, sharing a single Worker object, could be starting and stopping the thread of the Worker object. However, this generalization also requires that the field theThread is declared volatile in order to avoid memory consistency errors (p. 1414).
Example 22.8 Thread Termination
public class Worker implements Runnable { // (1)
private Thread theThread; // (2)
public void kickStart() { // (3)
if (theThread == null) {
theThread = new Thread(this);
theThread.start();
}
}
public void terminate() { // (4)
theThread = null;
}
@Override
public void run() { // (5)
while (theThread == Thread.currentThread()) { // (6)
System.out.println(“Going around in loops.”);
}
}
}
public class Controller {
public static void main(String[] args) { // (7)
Worker worker = new Worker(); // (8)
System.out.println(“Start the worker.”);
worker.kickStart(); // (9)
try {
Thread.sleep(2); // (10)
} catch(InterruptedException ie) {
ie.printStackTrace();
}
System.out.println(“Stop the worker.”);
worker.terminate(); // (11)
}
}
Probable output from the program:
Start the worker.
Going around in loops.
Going around in loops.
…
Going around in loops.
Going around in loops.
Stop the worker.
Going around in loops.