Why ExecutorService method invokeAny() handles different amount of tasks on every program run?

Viewed 120

Look at this code:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class InvokeAny {
    public static void main(String[] args) {
        Callable<String> callableTask = () -> {
            TimeUnit.MILLISECONDS.sleep(300);
            System.out.println("Callable task's execution");
            return "Task's execution";
        };

        List<Callable<String>> callableTasks = new ArrayList<>();
        callableTasks.add(callableTask);
        callableTasks.add(callableTask);
        callableTasks.add(callableTask);

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        try {
            executorService.invokeAny(callableTasks);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        shutdownAndAwaitTermination(executorService);
    }

    private static void shutdownAndAwaitTermination(ExecutorService pool) {
        pool.shutdown(); // Disable new tasks from being submitted
        try {
            // Wait a while for existing tasks to terminate
            if (!pool.awaitTermination(1000, TimeUnit.MILLISECONDS)) {
                pool.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if (!pool.awaitTermination(1000, TimeUnit.MILLISECONDS))
                    System.err.println("Pool did not terminate");
            }
        } catch (InterruptedException ie) {
            // (Re-)Cancel if current thread also interrupted
            pool.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }
}

Every time I run my program, I get different results in console.

1st run:

Callable task's execution

2nd run:

Callable task's execution
Callable task's execution

3rd run:

Callable task's execution

Could anybody explain me why it happens?

In Oracle's documentation there is only one phrase about method invokeAny(Collection<? extends Callable<T>> tasks):

Executes the given tasks, returning the result of one that has completed successfully (i.e., without throwing an exception), if any do.

I want to understand how it works. Does it cancel remaining tasks after one was completed? If it does, why sometimes I get 2 tasks being executed?

1 Answers

Does it cancel remaining tasks after one was completed?

Yes, that's right, but that does not mean it will submit/start the next task only after the current task is finished, that's the whole point of concurrency, it does not wait for the previous task to complete. Submit the tasks one by one, don't wait for them to complete, meanwhile, check if any task is completed, if completed, cancel all currently running tasks, don't submit the remaining tasks and just return the completed task.

Now, before cancelling the running tasks finally, its possible they have done their job or they may not, in your case the print statement, depending on the time slice each thread gets, this is based on various JVM and System factors as pointed out in the comment.

Related