[FrontPage] [TitleIndex] [WordIndex

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:

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:


CategoryDeegree3


2018-04-20 12:05