Table of Contents
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:
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.
I build softwares that solve problems. I also love writing/documenting things I learn/want to learn.