DEV Community

Code Craft Club
Code Craft Club

Posted on • Originally published at Medium

Multi-Threading in JAVA using Callables

Introduction to Callables

In multi-threaded programming, we often encounter scenarios where tasks need to be executed concurrently, but they don't necessarily need to return any values. Think of these tasks as workers performing their jobs independently, like chefs work simultaneously to execute their tasks, and they don't need to return any specific information. In such cases, runnables can be used where there is nothing to return.

Kitchen

However, there are situations where not only do we need tasks to run concurrently, but we also need them to return some valuable data upon completion. This is where the concept of Callables comes into play.

Let’s understand with an example:

Online Shopping:

Imagine you are an online shopper placing multiple orders. You want to track the status of each order and get confirmation when each order is successfully processed. This is where the concept of Callable comes in.

Shopping

  • Callable is like each online order you've placed. These are the tasks you care about because you want to track the status of your orders.
  • Future is like the shipping confirmation email you receive for each order. When an order is shipped (the task is completed), you get an email (Future) that tells you the order's status and provides tracking information.

Using Callables in Multi-Threaded Programs

To incorporate Callables into multi-threaded programs, you can follow these steps:

Step 1: Identify Tasks Needing Data Returns

Identify tasks within your program that require data to be returned upon completion. These tasks are analogous to online shopping orders where you want tracking information.

Step 2: Create Callable Classes

For tasks that need to return data, create classes and implement the Callable interface. This interface allows tasks to return results or throw exceptions, making it suitable for data-retrieval tasks.

import java.util.concurrent.Callable;

class OrderProcessingTask implements Callable<String> {
    private String item;

    public OrderProcessingTask(String item) {
        this.item = item;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement the call() Method

Inside your Callable class, implement the call() method, which contains the code for the task and defines the return type for the method. This method represents the work that needs to be done and returns the result upon completion.

@Override
public String call() throws Exception {
    // Simulate order processing
    Thread.sleep(2000); // Simulating processing time
    return "Order for " + item + " is processed.";
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a Main Class Using Executor Service

Create a main class that utilizes the ExecutorService to execute the Callable tasks:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class OnlineShoppingApp {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // Create order processing tasks
        Callable<String> order1 = new OrderProcessingTask("Product A");
        Callable<String> order2 = new OrderProcessingTask("Product B");
        Callable<String> order3 = new OrderProcessingTask("Product C");

        // Submit tasks to the executor
        Future<String> result1 = executor.submit(order1);
        Future<String> result2 = executor.submit(order2);
        Future<String> result3 = executor.submit(order3);

        // Wait for tasks to complete and retrieve results
        String status1 = result1.get();
        String status2 = result2.get();
        String status3 = result3.get();

        System.out.println(status1);
        System.out.println(status2);
        System.out.println(status3);

        // Shutdown the executor when done
        executor.shutdown();
    }
}
Enter fullscreen mode Exit fullscreen mode

Understanding the use of Future with Callable

In the main class, we use Future objects to capture the results of the submitted Callable tasks. A Future represents the result of an asynchronous computation, in this case, the result of each order processing task.

Analogy:

In our example scenario of Online Shopping, each order represents a separate task, and we want to keep track of their status and delivery. So:

  • Callable is analogous to an online shopping order. Each order represents a task that we want to track.
  • Future is like the order confirmation or tracking number we receive after placing an online order. It’s a reference to our order’s status and allows us to retrieve details when the order is processed.

Just like you can check the status of our online orders using the order number without waiting at the store, we can use Future<> to check the status and retrieve results from Callable tasks without blocking the program.

Here's how this works:

  • executor.submit(callable) returns a Future representing the result of the callable task.
  • result.get() is called to retrieve the result of the task.

This allows to efficiently manage tasks that return data in a multi-threaded program. The Future provides a way to obtain the result of the task when it's ready, just like tracking online orders and receiving status updates when the orders are processed.

Closing Thoughts:

Thank you for reading our blog. We appreciate your time and interest in our content. If you have any questions, feedback, or topics you’d like us to cover in our future articles, please feel free to leave a comment below. Your input is valuable, and it helps us create content that matters to you.

Stay connected with us for more insightful articles on various topics related to technology, programming, and much more.

Happy coding!

Top comments (0)