2018-03-22

Java threads and exceptions

A cup of coffee

Talking about tasks and exceptions in Ada, what about Java?

Java's behaviour when a task raises an exception is like Ada and not like C++. First of all, it is easy to make a task communicate with another one using a blocking queue1, because there's already a standard implementation. This “machinery” can be used to write code comparable to what you'd do in Go (using channels) or in Ada using select/accept2.

The Ada Waitress task is a message consumer in the following Java code3.

import java.util.concurrent.LinkedBlockingQueue;

class MessageConsumer<T extends Comparable<String>>
    extends Thread
{
    private LinkedBlockingQueue<T> mq;

    public MessageConsumer(LinkedBlockingQueue<T> q) {
        mq = q;
    }

    public void run() {
        while (true) {
            try {
                T p = mq.take();
                if (p == "Go") {
                    System.out.println("*** stopping thread ***");
                    Thread.sleep(10*1000);
                    System.out.println("*** almost stopped ***");
                    break;
                } else {
                    System.out.println("Msg *** " + p + " ***");
                }
            } catch (InterruptedException e) {
                System.out.println("*** interrupted ***");
                break;
            }
        }
    }
}

When the queue is empty, take() blocks. The code of the “main task” is:

    public static void main(String[] arg) throws InterruptedException {
        LinkedBlockingQueue<String> q = new LinkedBlockingQueue<>(10);
        Thread t = new MessageConsumer<String>(q);      
        try {
            t.start();
            q.put("hello");
            q.put("there");
            Thread.sleep(2*1000);
            System.out.println(arg[0]);
        } catch (NullPointerException e) {
            System.out.println("*** null ***");
        }
    }

After the task is created and started, two messages are enqueued (just to read something in the console), then the code pauses for 2 seconds and then we try to access the first argument. If you didn't give a first argument, arg[0] throws an ArrayIndexOutOfBoundException, which is uncaught. Then, it hangs because the thread isn't terminated, but instead it keeps waiting a “message” from the queue (it is blocked on take()).

The output is

Msg *** hello ***
Msg *** there ***
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at TaskWhat.main(TaskWhat.java:42)

(My class name is TaskWhat, but this is irrelevant.)

One of the messages could be "Go":

            q.put("hello");
            q.put("there");
            q.put("Go");
            Thread.sleep(2*1000);
            System.out.println(arg[0]);

This time the output will be:

Msg *** hello ***
Msg *** there ***
*** stopping thread ***
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at TaskWhat.main(TaskWhat.java:43)
*** almost stopped ***

The almost stopped string appears 8 seconds (more or less) after the exception.

How to kill them

In Ada the or terminate clause caused the child task/thread to finish because of the uncaught exception raised in the parent (environment task).

In Java it seems we fall on the “collaborative path”: the thread must catch InterruptedException and “break” its activity (in the example a literal break does; without it the thread happily loops again and blocks on take()), and the parent must interrupt() its children.

Overriding run() we must catch the InterruptedException — we can't say run() throws it because the overridden run() doesn't throw. This “forces” the way already shown in the example.

In the main we must catch exceptions and interrupt() those threads which needs to be interrupted, e.g.:

        try {
            // ...
        } catch (NullPointerException e) {
            System.out.println("*** null ***");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("<<< NEEE >>>");
            t.interrupt();
        }

In the thread the exception is thrown by specific functions which cause the thread to wait (take() and sleep() in the examples). If the thread is in the middle of a long computation which should be nonetheless interrupted as soon as possible, it won't stop unless the programmer has taken this event into account using Thread.interrupted() (see e.g. here).


  1. You can implement a thread-safe blocking queue in C++ using POSIX threads “primitives” too, but there isn't a ready-to-use class. For testing this example could do its job.

  2. In Ada it is like you are calling a procedure, which may have arguments; using channels (or queues) likely you don't want to enqueue strings as I do in this example.

  3. Because of how I intend to use it, the generic type T must extends Comparable<String>. The aim of this example isn't to imitate Ada or Go, but to see what Java tasks “do” when their parent dies because of an uncaught exception.

No comments:

Post a Comment