Understating Java Future and Callable Features

--

Hi All ,

Today, We will go through an Overview of Futures and Callable Features in Java . Let’s Get Started ..

Java Future , Callable Features

java.util.concurrent.Future is an interface that represents the result of an asynchronous computation. It allows you to cancel a task, check if it has completed, and retrieve the result of the computation.

java.util.concurrent.Callable is an interface that represents a task that can be executed concurrently and returns a result. It is similar to the java.lang.Runnable interface, but it can return a value and throw a checked exception.

You can use Future and Callable together to perform concurrent tasks and retrieve the results in a thread-safe manner.

In this post, we will look at some examples of using Future and Callable in Java, including handling exceptions, cancelling tasks, chaining tasks, and waiting for multiple tasks to complete.

To use Future and Callable, you will typically need an Executor or an ExecutorService, which is responsible for executing tasks concurrently. The java.util.concurrent package provides several implementations of these interfaces, such as ThreadPoolExecutor and ForkJoinPool.

To submit a Callable task to an ExecutorService, you can use the submit() method, which returns a Future object that represents the result of the computation.

You can then use the get() method of the Future object to retrieve the result of the task, either blocking until the task completes or timing out after a specified amount of time.

You can also use the CompletableFuture class, which is an implementation of Future that provides additional methods for defining and composing asynchronous tasks. For example, you can use the thenApply() method to chain multiple tasks together, or the allOf() method to wait for multiple tasks to complete.

Java’s java.util.concurrent package provides several useful utility classes for working with concurrency.

One such class is java.util.concurrent.Future, which represents the result of an asynchronous computation. A Future object can be used to check if the computation is complete, retrieve the result of the computation, or wait for the computation to complete.

Here is an example of using a Future to retrieve the result of an asynchronous computation:

import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();

Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Hello World!";
}
});
System.out.println("Waiting for the result...");
String result = future.get();
System.out.println(result);
executor.shutdown();
}
}

In this example, we use an ExecutorService to submit a Callable task for execution. The Callable interface is similar to the Runnable interface, except that it can return a result and throw checked exceptions. The submit() method returns a Future object, which we can use to retrieve the result of the computation.

The get() method blocks until the computation is complete and then returns the result. If the computation throws an exception, the get() method will throw an ExecutionException. If the thread is interrupted while waiting for the result, the get() method will throw an InterruptedException.

There are also several other methods available in the Future interface, such as isDone(), which returns true if the computation is complete, and cancel(), which attempts to cancel the computation.

Using Future and Callable can make it easier to write concurrent programs in Java, as it allows you to manage the results of asynchronous computations in a more structured way.

Details on java.util.concurrent.Future :

Future is a generic interface that represents the result of an asynchronous computation. It has the following methods:

  • boolean cancel(boolean mayInterruptIfRunning): Attempts to cancel the computation. If the computation has already completed or cannot be cancelled, this method returns false. Otherwise, it returns true.
  • boolean isCancelled(): Returns true if the computation was cancelled before it completed normally.
  • boolean isDone(): Returns true if the computation has completed, whether it completed normally, was cancelled, or terminated due to an exception.
  • V get() throws InterruptedException, ExecutionException: Waits if necessary for the computation to complete, and then retrieves its result. If the computation was cancelled, this method throws a CancellationException. If the computation completed due to an exception, this method throws an ExecutionException. If the current thread was interrupted while waiting for the result, this method throws an InterruptedException.
  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException: Waits the specified amount of time for the computation to complete, and then retrieves its result. If the computation has not completed within the specified time, this method throws a TimeoutException. Otherwise, it behaves the same as the get() method.

Details on java.util.concurrent.Callable:

Callable is a functional interface that represents a task that can return a result and throw checked exceptions. It has a single method:

  • V call() throws Exception: Computes a result.
  1. Here is an example of using a Callable to compute the sum of the first 100 numbers:
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
});
System.out.println("Waiting for the result...");
int result = future.get();
System.out.println("The sum of the first 100 numbers is: " + result);
executor.shutdown();
}
}

2. Handling Exceptions

If a Callable task throws an exception, it will be wrapped in an ExecutionException and thrown by the get() method of the Future object. You can handle this exception by using a try-catch block:

try {
int result = future.get();
} catch (ExecutionException e) {
// Handle the exception thrown by the Callable task
}

Alternatively, you can use the get() method that takes a timeout and a TimeUnit argument. This method will throw a TimeoutException if the computation has not completed within the specified time.

try {
int result = future.get(1, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException e) {
// Handle the exception thrown by the Callable task or the timeout
}

3. Cancelling Tasks

You can cancel a Future task using the cancel() method. This method returns true if the task was successfully cancelled, and false if the task could not be cancelled or had already completed.

if (future.cancel(true)) {
System.out.println("Task cancelled successfully");
} else {
System.out.println("Task could not be cancelled");
}

Note that cancelling a task does not guarantee that it will stop immediately. It is up to the implementation of the Callable task to check the isCancelled() method and stop executing if necessary.

You can use the cancel() method of the Future interface to attempt to cancel the execution of a task. If the task has already started, it may not be possible to cancel it. The isCancelled() method returns true if the task was successfully cancelled, and false otherwise.

Here is an example of cancelling a Future task:

import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
try {
Thread.sleep(1000);
return "Hello World!";
} catch (InterruptedException e) {
return "Interrupted";
}
});
future.cancel(true);
if (future.isCancelled()) {
System.out.println("Task was cancelled");
} else {
System.out.println(future.get());
}
executor.shutdown();
}
}

In this example, we use the submit() method of the ExecutorService to submit a Callable task that sleeps for 1 second and returns a string. We then use the cancel() method to attempt to cancel the task. The isCancelled() method returns true if the task was successfully cancelled, and false otherwise.

4. Waiting for a Future to Complete

You can use the get() method of the Future interface to block the current thread until the task completes, and then retrieve the result of the computation. The get() method can throw a checked ExecutionException if the task threw an exception, or a checked InterruptedException if the current thread was interrupted while waiting for the task to complete.

import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
while (!Thread.currentThread().isInterrupted()) {
// Do some work
}
return "Task cancelled";
}
});
// Cancel the task after 1 second
Thread.sleep(1000);
future.cancel(true);
System.out.println("Waiting for the result...");
String result = future.get();
System.out.println(result);
executor.shutdown();
}
}

You can also use the get(long timeout, TimeUnit unit) method to specify a timeout

5.Handling Timeouts

You can use the get(long timeout, TimeUnit unit) method of the Future interface to specify a timeout and a TimeUnit for waiting for the task to complete. If the task does not complete within the specified timeout, the get() method will throw a checked TimeoutException.

Here is an example of using the get(long timeout, TimeUnit unit) method to handle a timeout:


import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
try {
Thread.sleep(2000);
return "Hello World!";
} catch (InterruptedException e) {
return "Interrupted";
}
});
try {
String result = future.get(1, TimeUnit.SECONDS);
System.out.println(result);
} catch (TimeoutException e) {
System.out.println("Timeout");
}
executor.shutdown();
}
}

In this example, we use the submit() method of the ExecutorService to submit a Callable task that sleeps for 2 seconds and returns a string. We then use the get(long timeout, TimeUnit unit) method to specify a timeout of 1 second and a `TimeUnit’.

6.Chaining Futures

You can use the thenApply() method of the CompletableFuture class to chain multiple Future tasks together. This method takes a Function argument, which is applied to the result of the previous Future task, and returns a new CompletableFuture that completes with the result of the function.

import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
return "Hello";
} catch (InterruptedException e) {
return "Interrupted";
}
}, executor);
CompletableFuture<String> future2 = future1.thenApplyAsync(s -> s + " World!", executor);
CompletableFuture<String> future3 = future2.thenApplyAsync(s -> s + "!", executor);
System.out.println(future3.get()); // "Hello World!"
executor.shutdown();
}
}

In this example, we use the supplyAsync() method of the CompletableFuture class to create an asynchronous task that sleeps for 1 second and returns a string. We then chain three Future tasks together using the thenApplyAsync() method, which applies a function to the result of the previous task and returns a new CompletableFuture.

The thenApplyAsync() method takes an Executor argument, which specifies the executor that will be used to execute the task. In this case, we use the same ExecutorService for all tasks.

Finally, we use the get() method of the last Future task to retrieve the result of the computation.

7.Asynchronous Callbacks

You can use the thenAccept() method of the CompletableFuture class to specify a callback that will be executed when the Future task completes. This method takes a Consumer argument, which is applied to the result of the Future task.

Here is an example of using the thenAccept() method to print the result of a Future task:

 import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
return "Hello World!";
} catch (InterruptedException e) {
return "Interrupted";
}
}, executor);
future.thenAccept(System.out::println);
executor.shutdown();
}

In this example, we use the supplyAsync() method to create an asynchronous task that sleeps for 1 second and returns a string. We then use the thenAccept() method to specify a callback that will print the result of the task.

Note that the thenAccept() method does not return a new CompletableFuture, so it cannot be used to chain multiple tasks together.

8.Combining Futures

You can use the thenCombine() method of the CompletableFuture class to combine the results of two Future tasks. This method takes another CompletableFuture and a BiFunction argument, which is applied to the results of both Future tasks, and returns a new CompletableFuture that completes with the result of the function.

Here is an example of using the thenCombine() method to compute the sum of two Future tasks:

import java.util.concurrent.*;

public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
return 10;
} catch (InterruptedException e) {
return 0;
}
}, executor);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
return 20;
} catch (InterruptedException e) {
return 0;
}
}, executor);
CompletableFuture<Integer> future3 = future1.thenCombine(future2, (x, y) -> x + y);
System.out.println(future3.get()); // 30
executor.shutdown();
}
}

In this example, we use the supplyAsync() method to create two asynchronous tasks that sleep

9.Handling Exceptions in Callbacks

You can use the exceptionally() method of the CompletableFuture class to specify a callback that will be executed if the Future task completes exceptionally (i.e., if it throws an exception). This method takes a Function argument, which is applied to the exception thrown by the task, and returns a default value to be used as the result of the CompletableFuture.

Here is an example of using the exceptionally() method to handle an exception thrown by a Future task:

import java.util.concurrent.*;

public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Something went wrong");
}, executor);
CompletableFuture<String> future2 = future.exceptionally(ex -> {
System.out.println("Exception: " + ex.getMessage());
return "Default value";
});
System.out.println(future2.get()); // "Default value"
executor.shutdown();
}
}

In this example, we use the supplyAsync() method to create an asynchronous task that throws an exception. We then use the exceptionally() method to specify a callback that will be executed if the task throws an exception. The callback prints the exception message and returns a default value to be used as the result of the CompletableFuture.

10.Waiting for Multiple Futures

You can use the allOf() method of the CompletableFuture class to create a new CompletableFuture that will complete when all of the specified Future tasks have completed. This method takes an array of CompletableFuture objects and returns a new CompletableFuture that completes with the results of all of the tasks.

Here is an example of using the allOf() method to wait for multiple Future tasks:

import java.util.concurrent.*;

public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
return "Task 1";
} catch (InterruptedException e) {
return "Interrupted";
}
}, executor);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
return "Task 2";
} catch (InterruptedException e) {
return "Interrupted";
}
}, executor);
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
return "Task 3";
} catch (InterruptedException e) {
return "Interrupted";
}
}, executor);
CompletableFuture<Void> allTasks = CompletableFuture.allOf(future1, future2, future3);
allTasks.get(); // Wait for all tasks to complete
System.out.println(future1.get()); // "Task 1"
System.out.println(future2.get()); // "Task 2"
System.out.println(future3.get()); // "Task 3"
executor.shutdown();
}
}

In this example, we use the supplyAsync() method to create three asynchronous tasks that sleep for different amounts of time and return a string. We then use the allOf() method to create a new CompletableFuture that will complete when all of the tasks have completed.

We use the get() method of the allTasks CompletableFuture to wait for all tasks to complete. Then, we use the get() method of each individual Future task to retrieve the result of the computation.

Conclusion :

I hope this discussion on using java.util.concurrent.Future and java.util.concurrent.Callable in Java has been helpful. As you can see, these interfaces provide a powerful and flexible way to perform concurrent tasks and retrieve the results in a thread-safe manner.

References:

We hope you liked this post on an Overview of Futures and Callable Features in Java. Thank you for reading!

Happy Learning … Happy Coding …..

Other Interesting Articles:

Effective Java Development with Lombok

AWS Lambda in Action

AWS SOAR: Enhancing Security with Automation

Java : Understanding The Golden Ration Phi

AWS Learning : Journey towards Limitless Opportunities in Cloud .

No-cost ways to learn AWS Cloud over the holidays

Understanding 𝗖𝗢𝗥𝗦-𝗖𝗿𝗼𝘀𝘀-𝗢𝗿𝗶𝗴𝗶𝗻 𝗥𝗲𝘀𝗼𝘂𝗿𝗰𝗲 𝗦𝗵𝗮𝗿𝗶𝗻𝗴

Linux Commands for Cloud Learning

Java Programming Principles : Law of Demeter

--

--

Gaurav Rajapurkar - A Technology Enthusiast

An Architect practising Architecture, Design,Coding in Java,JEE,Spring,SpringBoot,Microservices,Apis,Reactive,Oracle,Mongo,GCP,AWS,Kafka,PubSub,DevOps,CI-CD,DSA