Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

future.cancel does not work

I have a nice and compact code, which does not work as I expected.

public class Test {

    public static void main(String[] args) {

        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    for (;;) {

                    }
                } finally {
                    System.out.println("FINALLY");
                }

            }
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<?> future = executor.submit(r);
        try {
            future.get(3, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            boolean c = future.cancel(true);
            System.out.println("Timeout " + c);
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("interrupted");
        }
        System.out.println("END");

    }

}

The output is :

Timeout true

END

Question: Why does not terminate the future.cancel(true) method the called Runnable? After the program wrote the "END" to the output, the "r" Runnable is still running.

like image 899
teglus Avatar asked Jan 20 '15 10:01

teglus


3 Answers

The problem is that your Runnable is not interruptible: task interruption is a collaborative process in Java and the cancelled code needs to check regularly if it's been cancelled, otherwise it won't respond to the interruption.

You can amend you code as follows and it should work as expected:

Runnable r = new Runnable() {
    @Override public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {}
        } finally {
            System.out.println("FINALLY");
        }
    }
};
like image 152
assylias Avatar answered Nov 02 '22 10:11

assylias


This is always a little bit misleading: The ExceutorService or even the underlying thread scheduler do not know anything about what the Runnable is doing. In your case they don't know that there is a unconditional loop.

All these methods (cancel, done, ...) are related to manage Threads in the Executor structure. cancel cancels the thread from the point of view of the Executor service.

The programmer must test if the Runnable was canceled and must terminate the run() method.

So in your case (if I remember well) something like this:

public class Test {

public static void main(String[] args) {

    FutureTask r = new FutureTask () {
        @Override
        public void run() {
            try {
                for (;!isCancelled();) {

                }
            } finally {
                System.out.println("FINALLY");
            }

        }
    };

    ExecutorService executor = Executors.newSingleThreadExecutor();

    Future<?> future = executor.submit(r);
    try {
        future.get(3, TimeUnit.SECONDS);
    } catch (TimeoutException e) {
        boolean c = future.cancel(true);
        System.out.println("Timeout " + c);
    } catch (InterruptedException | ExecutionException e) {
        System.out.println("interrupted");
    }
    System.out.println("END");

}

}

like image 2
PeterMmm Avatar answered Nov 02 '22 11:11

PeterMmm


When you cancel a Future whose Runnable has already begun, the interrupt method is called on the Thread that is running the Runnable. But that won't necessarily stop the thread. Indeed, if it's stuck in a tight loop, like the one you've got here, the Thread won't stop. In this case, the interrupt method just sets a flag called the "interrupt status", which tells the thread to stop when it can.

See the Javadoc for the interrupt method of Thread

like image 1
Dawood ibn Kareem Avatar answered Nov 02 '22 11:11

Dawood ibn Kareem