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.