Runnable vs Callable in Java
In Java, there are two main interfaces that are used to define tasks that can be executed concurrently — Runnable and Callable. Both of these interfaces serve as essential tools for managing threads in Java, but they have distinct characteristics and use cases. Understanding the differences between these two interfaces is important for writing efficient multithreaded applications in Java.
Runnable
The Runnable interface is part of the java.lang
package and represents a task that can be executed concurrently by a thread. It defines a single abstract method, run()
, which contains the code that will be executed when the thread is started.
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Runnable is a simple and straightforward way to create and start a new thread in Java. It doesn't return any result after execution, making it suitable for tasks that are meant to run independently. To create a runnable task, you simply need to implement the run() method. For example:
public class MyRunnable implements Runnable {
@Override
public void run() {
// task code here
}
}
You can then pass an instance of MyRunnable to a Thread constructor to execute it asynchronously.
Callable
On the other hand, the Callable interface, introduced in Java 5, is part of the java.util.concurrent
package. It is a more advanced alternative to Runnable. Unlike Runnable, Callable's main method, call()
, can return a result or throw an exception.
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
This feature allows for more complex and robust thread management, as you can obtain the results of the execution and handle exceptions gracefully. Callable is often used when you need to perform tasks concurrently and collect their results or if you need more control over the execution. Callable also works with Java’s ExecutorService to submit tasks and receive Future objects representing the pending result of computation. For example:
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// task code
return result;
}
}
// submit Callable to ExecutorService
Future<Integer> future = executorService.submit(new MyCallable());
// retrieve result later
int result = future.get();
Callable can only be executed by ExecutorService. But Runnable can be executed by Runnable and the ExecutorService. Because Thread is an implementation of a Runnable.
Differences of Runnable and Callable
Return Value
The most significant difference between Runnable and Callable is the return value. Runnable’s run()
method doesn't return anything, whereas Callable's call()
method can return a value (or throw an exception).
Exception Handling
With Runnable, you have limited control over exceptions. Runnable’s run() method definition does not throw any exceptions, so any Checked Exceptions need to be handled in the run() implementation method itself. Any unhandled exception will propagate to the thread’s uncaught exception handler. In contrast, Callable allows you to capture exceptions and handle them appropriately within your code.
Usage with Executors
Both Runnable and Callable can be submitted to executor services (e.g., ExecutorService
or ThreadPoolExecutor
). Runnable tasks can be submitted using the execute()
method, whereas Callable tasks are submitted with the submit()
method. The latter returns a Future
object that can be used to retrieve the result.
Runnable Example
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunnableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable task = () -> {
System.out.println("This is a Runnable task.");
};
executor.execute(task);
executor.shutdown();
}
}
In this Runnable example, we create a simple task and execute it using an ExecutorService
.
// Runnable that cannot return result
class GetDataRunnable implements Runnable {
@Override
public void run() {
// call API
String result = httpClient.sendRequest();
// process result
processString(result);
}
}
public class RunnableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new GetDataRunnable());
executor.shutdown();
}
}
In the above example, code shows how we might use Runnable to fetch data from a remote API and process the result.
Callable Example
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<String> task = () -> {
Thread.sleep(2000);
return "This is a Callable task.";
};
Future<String> result = executor.submit(task);
try {
String message = result.get();
System.out.println(message);
} catch (Exception e) {
e.printStackTrace();
}
executor.shutdown();
}
}
In the Callable example, we execute a task that returns a result, which we retrieve using the Future
object. We also demonstrate exception handling.
// Callable that returns result
class GetDataCallable implements Callable<String> {
@Override
public String call() throws Exception {
// call API
return httpClient.sendRequest();
}
}
public class RunnableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
// submit and receive future
Future<String> future = executorService.submit(new GetDataCallable());
// process future later
String result = future.get();
processString(result);
executor.shutdown();
}
}
In the above example, the code shows how we might use Callable to fetch data from a remote API and process the result.
In summary, Runnable is useful for fire-and-forget tasks that do not need to return results. On the other hand, Callable provides more control, allowing you to handle exceptions and obtain the result of the task, because it works with Futures. Understanding these differences allows you to pick the best interface to suit the concurrent needs of your application.
👏 Thank You for Reading!
👨💼 I appreciate your time and hope you found this story insightful. If you enjoyed it, don’t forget to show your appreciation by clapping 👏 for the hard work!
📰 Keep the Knowledge Flowing by Sharing the Article!
✍ Feel free to share your feedback or opinions about the story. Your input helps me improve and create more valuable content for you.
✌ Stay Connected! 🚀 For more engaging articles, make sure to follow me on social media:
🔍 Explore More! 📖 Dive into a treasure trove of knowledge at Codimis. There’s always more to learn, and we’re here to help you on your journey of discovery.