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 returnsfalse
. Otherwise, it returnstrue
.boolean isCancelled()
: Returnstrue
if the computation was cancelled before it completed normally.boolean isDone()
: Returnstrue
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 aCancellationException
. If the computation completed due to an exception, this method throws anExecutionException
. If the current thread was interrupted while waiting for the result, this method throws anInterruptedException
.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 aTimeoutException
. Otherwise, it behaves the same as theget()
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.
- 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 𝗖𝗢𝗥𝗦-𝗖𝗿𝗼𝘀𝘀-𝗢𝗿𝗶𝗴𝗶𝗻 𝗥𝗲𝘀𝗼𝘂𝗿𝗰𝗲 𝗦𝗵𝗮𝗿𝗶𝗻𝗴