Java: Runnable vs Callable

 

Runnable

There are two ways to create a thread

  1. Provide a Runnable object.
  2. Subclass Thread

Callable

A task that returns a result and may throw an exception. Implementors define a single method with no arguments called call.

Why Runnable and Callable?

Why there are two approaches to handle threads in Java? Let's dig further.
Imagine there is a single-threaded web server that can accept client requests. This server will definitely be very slow and inefficient, but it can be improved by utilizing threads. By using threads we can create a thread for each request to handle the request. Even with this approach, there are a few issues that could arise. Such as,
  1. Creating and teardown of threads for each request could cause resource exhaustion and make the system inefficient.
  2. Having many threads running at the same time could use more resources such as memory and CPU.
  3. The system could become unstable due to the thread limit. The number of threads a Java application can handle is not unlimited.
How to resolve the above issues?  We can use the Executor framework. It allows us to create thread pools. We can create tasks and submit those to the executor, then it will take care of it. These threads request the next task from a queue, execute it, and go back to waiting for another task.

executor-service
Using Executor framework instead of other approaches


The above scenario uses Runnable (able to use Thread as well), and it can also use callable. 

We can submit a Runnable or a Callable to ExecuterService and create a Future object. Future represents the lifecycle of a task. And methods can be used to check whether the task has been completed or been canceled, retrieve its result, and cancel the task. Check the example under Callable.

What are the differences between Runnable and Callable?
  1. Runnable was introduced in Java 1.0, and Callable was introduced in Java 1.5
  2. When using Runnable, we override the "run()" method and include whatever logic we want to execute in it, but when using callable, we override the "call()" method.
  3. Runnable's run method cannot return a value or throw checked exceptions.
  4. Instead, it can log or write data to a shared data structure when needed to return it.
  5. Callable's call method can return a value or throw exceptions.
    1. Suitable for executing database queries
    2. Suitable for Fetching a resource over the network
    3. Suitable for Computing a complicated function
  6. When a task doesn't return anything or throw exceptions, you can use Runnable instead of Callable<void> or returning null.
To ensure backward compatibility, the Java team did not change the Runnable.run() method when they could have and introduced Callable instead.

I highly recommend reading the "Java Concurrency in Practice" to get a broader understanding of Java concurrency.

References

  • Chapter 6 of https://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601
  • https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html
  • https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html

Comments