javaexecutorservicerunnablecallableexecutor

Make a `Callable` of my `Runnable` in Java


I have some tasks defined as Runnable objects. I want to invoke those tasks by using the invokeAll or invokeAny methods on ExecutorService.

The problem is that those methods take a collection of Callable objects (tasks that return a result) rather than a collection of Runnable objects (tasks that do not return a result).

Can I convert or wrap my Runnable objects to act as Callable objects without having to rewrite my task class?


Solution

  • Executors.callable

    Easy to solve.

    The Executors utility class offers a pair of callable methods that transform a Runnable into a Callable.

    One method produces null as the result of the task. The other returns your specified object as the result.

    Method Description
    Executors.callable(Runnable task) runs task, returns null
    Executors.callable(Runnable task, T result) runs task, returns the passed result

    Example code

    Let's start with some Runnable objects.

    Collection<Runnable> runnables =
            List.of(
                    new MyRunnable(),
                    new MyRunnable(),
                    new MyRunnable()
            );
    

    Convert each Runnable object into a Callable object that returns null as the result obtained via a Future object.

    We can use streams to write a one-liner for converting Runnable objects to Callable objects.

    Collection<Callable<Object>> tasks = runnables.stream().map(Executors::callable).toList();
    

    If you are not comfortable with streams, use for loop.

    Collection<Callable<Object>> tasks = new ArrayList<>();
    for (Runnable runnable : runnables) {
      Callable<Object> callable = Executors.callable(runnable);
      tasks.add(callable);
    }
    

    Let's execute those Callable task objects.

    Here we use try-with-resources syntax to automatically close the ExecutorService after it completes all submitted tasks.

    try (ExecutorService executorService = Executors.newCachedThreadPool()) {
      try {
        List<Future<Object>> futures = executorService.invokeAll(tasks);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
    

    When run:

    Running `run` method of `MyRunnable` id: bbc1b37a-dfc9-43da-8245-6a451b1d5517 at 2024-06-03T23:32:04.505167Z
    Running `run` method of `MyRunnable` id: fec26a2f-3b11-413b-a6c3-3bc802874302 at 2024-06-03T23:32:04.505167Z
    Running `run` method of `MyRunnable` id: d71fefe3-3387-4743-9c7d-20c338b84a33 at 2024-06-03T23:32:04.505163Z
    

    IF we were to loop through the futures collection, and call get, we would receive a null from each of them.


    By the way, notice how output from System.out.println does not necessarily appear on the console in chronological order when called across threads.