[Java Concurrency] 03: Callable

In the previous article, you’ve already known about the Runnable interface. To execute a task, you can simply create a class implement Runnable and pass an instance of that class in a thread.

Runnable instances don’t provide a return value, you may need a different solution if your use case requires capturing return values.

Introducing Callable

Similar to Runnable, the Callable interface is a functional interface. One of the key differences is you can return a value if your class implement Callable.

Let’s create a class implementing Callable:

package com.datmt.concurrency.java.simpletasks;

import java.util.concurrent.Callable;

public class D_SimpleCallableTask implements Callable<String> {
    private static int instanceCount;
    @Override
    public String call() throws Exception {
        try {

            System.out.println("START: D_SimpleCallableTask in "  + Thread.currentThread().getName() + " with instance #" + instanceCount);
            Thread.sleep(1000);

            System.out.println("DONE: D_SimpleCallableTask in "  + Thread.currentThread().getName()  + " with instance #" + instanceCount);

        } catch (InterruptedException ex) {
            ex.printStackTrace();
            return null;
        } finally {
            instanceCount++;
        }
        return  "Done calling in D_SimpleCallableTask at instance count: " + instanceCount;

    }
}

As you can see, the class D_SimpleCallableTask emulates a long running process (takes 1 second) and then returns a string.

But how do we get the return result of a callable?

Getting the result of callables

To execute a Callable instance, we don’t use thread. Instead, we will use an ExecutorService instance. Don’t worry if you don’t know about ExecutoreService yet. We will cover that in a later article.

package com.datmt.concurrency.java.executor;

import com.datmt.concurrency.java.simpletasks.D_SimpleCallableTask;

import java.util.concurrent.*;

public class D_Executor {


    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService service = Executors.newSingleThreadExecutor();
        Callable<String> task1 = new D_SimpleCallableTask();

        Future<String> task1Result = service.submit(task1);

        System.out.println("Task 1 result: " + task1Result.get());

    }
}

As you can see, the submit method of ExecutorService takes a Callable instance and returns a Future.

To get the result, simply call the get method on the Future instance. This method will wait, if necessary, to get the result of the call method of the Callable instance.

Throwing exceptions in Callable

Unlike Runnable, you can throw exceptions in Callable instances and catch them later in the caller’s thread.

For example, this code will NOT compile:

package com.datmt.concurrency.java.simpletasks;

public class D_SimpleRunnableTask implements Runnable {

    private static int instanceCount = 0;
    @Override
    public void run() {
        try {

            System.out.println("START: D_SimpleVoidTask in "  + Thread.currentThread().getName() + " with instance #" + instanceCount);
            Thread.sleep(1000);

            System.out.println("DONE: D_SimpleVoidTask in "  + Thread.currentThread().getName()  + " with instance #" + instanceCount);
            // Throwing exception is NOT OK in Runnable
            if (instanceCount < 1)
                throw  new Exception("Because I want to");
            instanceCount++;
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

But this will:

package com.datmt.concurrency.java.simpletasks;

import java.util.concurrent.Callable;

public class D_SimpleCallableTask implements Callable<String> {
    private static int instanceCount;
    @Override
    public String call() throws Exception {
        try {

            System.out.println("START: D_SimpleCallableTask in "  + Thread.currentThread().getName() + " with instance #" + instanceCount);
            Thread.sleep(1000);

            System.out.println("DONE: D_SimpleCallableTask in "  + Thread.currentThread().getName()  + " with instance #" + instanceCount);
            // Throwing exception is OK in Callable
            if (instanceCount < 1)
                throw  new Exception();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
            return null;
        } finally {
            instanceCount++;
        }
        return  "Done calling in D_SimpleCallableTask at instance count: " + instanceCount;

    }
}

In the caller’s thread, you can catch the exception like this:

package com.datmt.concurrency.java.executor;

import com.datmt.concurrency.java.simpletasks.D_SimpleCallableTask;

import java.util.concurrent.*;

public class D_Executor {


    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService service = Executors.newSingleThreadExecutor();
        Callable<String> task1 = new D_SimpleCallableTask();
        Callable<String> task2 = new D_SimpleCallableTask();

        try {
            Future<String> task1Result = service.submit(task1);

            System.out.println("Task 1 result: " + task1Result.get());
        } catch (Exception e) {
            System.out.println("got an exception " + e.getMessage());
        }

        Future<String> task2Result = service.submit(task2);

        System.out.println("Task 2 result: " + task2Result.get());
    }
}

Even when task1 threw an exception, task2 still running and produces the expected result:

Throwing execption and handling it in Callable

Conclusion

Callable is a very powerful interface that allows to execute long-running tasks and get the result. You can also throw exceptions in Callable and catch it later in the caller’s thread.

Leave a Comment