Programming cooperative threads
There is no good practice to cancel an uncooperative thread in Java, Thread.stop() has been deprecated long ago and for very good reasons. Sun explains this in detail at http://java.sun.com/javase/6/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html .
Basically, it turned out (after Thread.stop() had been introduced in JDK 1.0) that the forced killing of a Java tread may produce very nasty side effects, such as deadlocks (and probably resource handle leaks, e.g. file handles, network sockets or database connections). Therefore, Thread.stop() is bad (and may be removed in future JDK versions). Properly interruptable (=cancellable) Java code must be programmed in a cooperative way, i.e. it must check for interrupt requests and cope with InterruptedExceptions.
1. How cancellation of cooperative threads works
The usual way to signal a thread t that it should terminate is by calling t.interrupt(). This has two effects on thread t:
- t.isInterrupted() delivers true
if thread t is currently executing a blocking call (e.g. an I/O call or Thread.sleep()), the call is ended prematurely and an InterruptedException is thrown
Here's an example from Core Java (http://www.horstmann.com/corejava/cj7v2ch1.pdf) that illustrates the basic recipe for writing an interruptible method:
public void run()
{
try
{
...
while (!Thread.currentThread().isInterrupted() && more work to do)
{
do more work
}
}
catch(InterruptedException e)
{
// thread was interrupted during sleep or wait
}
finally
{
cleanup, if required
}
// exiting the run method terminates the thread
}
}
The example from Sun is functionally very similar, but uses a variable to communicate the termination request (and the same InterruptedException that is thrown in blocking calls as above). This is because (as Sun states) a call to Thread.currentThread() is rather expensive. They suggest the following:
private volatile Thread blinker;
public void stop() {
// moribund means "todgeweiht" in german
Thread moribund = waiter;
waiter = null;
moribund.interrupt();
}
public void run() {
Thread thisThread = Thread.currentThread();
while ( blinker == thisThread ) {
try {
thisThread.sleep( interval );
} catch ( InterruptedException e ) {
}
repaint();
}
}
2. What about java.util.concurrent.ExecutorService / deegree2 Executor?
One may wonder if the above statement (that killing uncooperative threads is not possible) is valid for the invokeAll() and invokeAnyMethods() of java.util.concurrent.ExecutorService. Both methods allow the specification of an optional timeout and it is guaranteed that the method call never takes longer than this timeout. How is this possible if Java has no method to cancel the execution of uncooperative code? (BTW, these methods are the heavily used in the org.deegree.framework.concurrent package in deegree2)
The basic trick is, that the task is performed in a different thread, and if it takes longer than timeout, the invoke-method returns and the task-thread is interrupted (in order to cancel it). But one may well write tasks, that keep on running forever in the background. Here's a simple example:
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TimeoutTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService execService = Executors.newCachedThreadPool();
System.out
.println("Main thread: Using Executor to invoke an Uncooperative Task with a timeout of 1000 milliseconds.");
Collection<Callable<Object>> tasks = new ArrayList<Callable<Object>>();
tasks.add(new UncooperativeTask ());
execService.invokeAll(tasks, 1000, TimeUnit.MILLISECONDS);
System.out.println("Main thread: exiting.");
}
}
class UncooperativeTask implements Callable<Object> {
@Override
public Object call() throws Exception {
long start = System.currentTimeMillis();
while (true) {
System.out.println("UncooperativeTask@" + (System.currentTimeMillis() - start)
+ ": Running.");
try {
Thread.sleep(250);
} catch (InterruptedException e) {
System.out.println("UncooperativeTask@" + (System.currentTimeMillis() - start)
+ ": Caught InterruptedException, but ignoring it.");
}
}
}
}
The output looks like this:
Main thread: Using Executor to invoking Uncooperative Task with a timeout of 1000 milliseconds. UncooperativeTask@0: Running. UncooperativeTask@250: Running. UncooperativeTask@501: Running. UncooperativeTask@751: Running. UncooperativeTask@1000: Caught InterruptedException, but ignoring it. UncooperativeTask@1000: Running. Main thread: exiting. UncooperativeTask@1250: Running. UncooperativeTask@1500: Running. UncooperativeTask@1750: Running. UncooperativeTask@2001: Running. UncooperativeTask@2251: Running. ...
As one can see, the invokeAll-call in the main thread returns just after the timeout has occured (and the main thread exits). However, the thread that runs the uncooperative task keeps on running forever. In this case it is really simple to fix this behaviour: don't catch the InterruptedException (but use it to leave the loop):
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TimeoutTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService execService = Executors.newCachedThreadPool();
System.out
.println("Main thread: Using Executor to invoke an Uncooperative Task with a timeout of 1000 milliseconds.");
Collection<Callable<Object>> tasks = new ArrayList<Callable<Object>>();
tasks.add(new CooperativeTask ());
execService.invokeAll(tasks, 1000, TimeUnit.MILLISECONDS);
System.out.println("Main thread: exiting.");
}
}
class CooperativeTask implements Callable<Object> {
@Override
public Object call() throws Exception {
long start = System.currentTimeMillis();
while (true) {
System.out.println("CooperativeTask@" + (System.currentTimeMillis() - start)
+ ": Running.");
Thread.sleep(250);
}
}
}
Now the output looks like:
Main thread: Using Executor to invoke an Uncooperative Task with a timeout of 1000 milliseconds. CooperativeTask@0: Running. CooperativeTask@250: Running. CooperativeTask@501: Running. CooperativeTask@751: Running. Main thread: exiting.
As one can see, the methods of ExecutorService cannot cancel uncooperative code as well -- these methods just use Thread.interrupt() internally. If the code does not cope correctly with InterruptedExceptions (i.e. it just catches and ignores them), threads keep on going forever.
Please note, that this is only one of the two aspects of writing interruptible code. If you don't call any methods that may throw InterruptedExceptions (such as Thread.sleep()), you have to check for the interruption request manually:
class CooperativeTask implements Callable<Object> {
@Override
public Object call() throws Exception {
long start = System.currentTimeMillis();
Thread currentThread = Thread.currentThread();
while (true) {
if (currentThread.isInterrupted()) {
throw new InterruptedException();
}
System.out.println("CooperativeTask@" + (System.currentTimeMillis() - start)
+ ": Running.");
}
}
}
3. Summary: Lessons to be learned for deegree3
There is no way to cancel uncooperative Java threads (without killing the whole VM) -- Thread.stop() can do it, but is deprecated. In order to support cancellation, this code must be implemented in a cooperative way, i.e. it must check for interrupt requests:
- If a thread t has to be cancelled, this is signalled by calling t.interrupt(). It is also a good practice to communicate the cancellation request by setting a flag that is visible in the thread's code.
- The flag variable (or Thread.currentThread.isInterrupted()) has to be periodically checked in the thread's code. When the thread notices the interruption request, it must clean up (release all held resources) and exit.
To support the premature end of blocking calls (e.g. I/O or sleeps), such operations throw an InterruptedException when someone calls t.interrupt(). This way, the call returns early (and an InterruptedException is thrown in the blocking method). The JVM and the Java libraries are responsible for throwing this exception. Therefore, it's a very bad practice to catch the InterruptedException and just do nothing, because it's the way the JVM signals the thread's code that it should exit.