Exploring Runnable, Callable and Future in multithreading

Phoenix Rivers
2 min readJul 4, 2024

--

Concurrency in Java is a powerful feature that allows multiple tasks to run in parallel, enhancing the performance and responsiveness of applications. Among the various constructs provided by Java for concurrent programming, Runnable, Callable, and Future stand out as fundamental and versatile tools. Let’s dive into these concepts and understand how they work together to make concurrent programming in Java more efficient and manageable.

Runnable

Runnable is one of the most basic interfaces for representing a task in Java that can be executed by a thread. It has a single method run() which contains the code that constitutes the task.

Key Points:

  • Runnable does not return a result.
  • It cannot throw a checked exception.

Example:

public class RunnableExample {
public static void main(String[] args) {
Runnable task = () -> System.out.println("Executing task in Runnable");
Thread thread = new Thread(task);
thread.start();
}
}

Callable

Callable is a more flexible and powerful alternative to Runnable. Introduced in Java 5, Callable is a generic interface with a call() method that can return a result and throw a checked exception.

Key Points:

  • Callable returns a result of type V.
  • It can throw a checked exception.

Example:

import java.util.concurrent.Callable;

public class CallableExample {
public static void main(String[] args) {
Callable<String> task = () -> {
return "Result from Callable";
};
// Normally you would submit this to an ExecutorService to execute.
}
}

Future

Future is an interface that represents the result of an asynchronous computation. It provides methods to check if the computation is complete, to wait for its completion, and to retrieve the result. Future can be used in conjunction with Callable to handle the results of concurrent tasks.

Key Points:

  • Future can be used to retrieve the result of a Callable task.
  • It allows checking if the task is completed or cancelled.

Example:

import java.util.concurrent.*;

public class FutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<String> task = () -> "Result from Callable";
Future<String> future = executor.submit(task);

if (future.isDone()) {
System.out.println("Task completed: " + future.get());
} else {
System.out.println("Task is still running");
}

executor.shutdown();
}
}

Combining Runnable, Callable, and Future

While Runnable is suitable for tasks that don’t return a result, Callable is the go-to for tasks that produce a result and can throw exceptions. Future serves as a bridge, allowing you to retrieve the result of a Callable task once it completes.

Practical Usage:

  • Runnable: Best for simple tasks that do not require a result.
  • Callable: Ideal for tasks that need to return a result or can throw exceptions.
  • Future: Useful for handling the results of asynchronous computations.

Conclusion

Understanding Runnable, Callable, and Future is essential for effective concurrent programming in Java. Runnable is great for simple tasks, while Callable adds flexibility with the ability to return results and throw exceptions. Future provides a way to manage and retrieve these results, making your concurrent applications more robust and efficient. By leveraging these constructs appropriately, you can significantly improve the performance and responsiveness of your Java applications.

--

--