Class 31: Getting Results with Future
Part 5 — Managing Threads with the Executor Framework
In the previous class, we learned about Runnable and Callable.
We understood that Runnable is used when a task does not need to return a result, whereas Callable can return a result.
Now let's consider a practical situation.
We submit a Callable task to an ExecutorService. The task runs on a worker thread and calculates something.
How do we get the result back?
Java provides an interface called Future for this purpose.
Let's understand it step by step.
1. What Is Future?
A Future represents the result of a task that may finish at some point in the future.
Think about ordering food online.
You place an order, and the restaurant starts preparing it. You do not receive the food immediately.
Instead, you receive an order reference that lets you check the status of your order and eventually receive the food.
A Future works in a similar way.
- We submit a task to an ExecutorService.
- The task starts executing.
- We receive a Future object.
- We can use that Future to check whether the task has completed.
- We can retrieve the result when it becomes available.
In simple words, Future is a reference to the result of an asynchronous task.
2. Submitting a Callable Task
Let's start with a simple example.
We will create a Callable task that calculates the sum of two numbers.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executorService =
Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
int a = 10;
int b = 20;
return a + b;
};
Future<Integer> future =
executorService.submit(task);
Integer result = future.get();
System.out.println("Result: " + result);
executorService.shutdown();
}
}
Let's understand the important lines.
Step 1: Create a Callable Task
Callable<Integer> task = () -> {
int a = 10;
int b = 20;
return a + b;
};
This task calculates the sum of two numbers and returns an Integer value.
Step 2: Submit the Task
Future<Integer> future =
executorService.submit(task);
We submit the Callable task using the submit()
method.
Notice that submit() returns a Future object.
Because our Callable returns an Integer, we use
Future<Integer>.
Step 3: Retrieve the Result
Integer result = future.get();
This is the most important line in our example.
The get() method retrieves the result of the task.
If the task has not finished yet, get() waits until the result becomes available.
Once the task completes, the result is returned.
In our example, the result will be:
Result: 30
3. Does future.get() Always Return Immediately?
No.
This is an important concept.
Imagine a task that takes five seconds to complete.
If we call future.get() immediately after
submitting it, our current thread may have to wait
for those five seconds.
The get() method is a blocking operation.
In simple words, the thread calling get() waits until the result is ready.
4. Understanding Blocking with an Example
Let's modify our example slightly.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureBlockingExample {
public static void main(String[] args) throws Exception {
ExecutorService executorService =
Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
System.out.println("Task started");
Thread.sleep(3000);
System.out.println("Task completed");
return 100;
};
Future<Integer> future =
executorService.submit(task);
System.out.println("Waiting for result");
Integer result = future.get();
System.out.println("Result: " + result);
executorService.shutdown();
}
}
Let's understand the execution.
- We submit the task to the ExecutorService.
- The task starts executing on a worker thread.
- The worker thread sleeps for three seconds.
- The main thread calls
future.get(). - The main thread waits until the task finishes.
- The result is returned, and the main thread continues.
One important thing to understand:
The worker thread and the main thread are different threads. The worker performs the task while the main thread waits for the result.
5. Checking Whether a Task Has Completed
What if we don't want to wait immediately?
What if we simply want to check whether the task has completed?
Future provides a method called isDone().
future.isDone();
It returns:
trueif the task has completed, was cancelled, or ended with an exception.falseif the task is still running or waiting to execute.
Let's see a simple example.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureIsDoneExample {
public static void main(String[] args) throws Exception {
ExecutorService executorService =
Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
Thread.sleep(3000);
return "Task completed";
});
System.out.println("Is task done? " + future.isDone());
String result = future.get();
System.out.println("Result: " + result);
System.out.println("Is task done? " + future.isDone());
executorService.shutdown();
}
}
When we check isDone() immediately after submission, it may return false because the task is still running.
After get() returns successfully, the task has completed, so isDone() will return true.
Remember that the first result is not guaranteed to be false. A very quick task may already have completed before we check it.
6. Retrieving a Result with a Time Limit
We have seen that get() waits until the task completes.
But sometimes we don't want to wait indefinitely.
Future provides another version of get() that accepts a time limit.
future.get(5, TimeUnit.SECONDS);
This means:
Wait for the result for a maximum of five seconds.
If the task does not complete within that time,
Java throws a TimeoutException.
Let's look at an example.
import java.util.concurrent.*;
public class FutureTimeoutExample {
public static void main(String[] args) {
ExecutorService executorService =
Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
Thread.sleep(5000);
return "Task completed";
});
try {
String result = future.get(2, TimeUnit.SECONDS);
System.out.println(result);
} catch (TimeoutException e) {
System.out.println("Task took too long!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
System.out.println("Task failed: " + e.getCause());
} finally {
executorService.shutdown();
}
}
}
Here, the task takes five seconds, but we wait for only two seconds.
Therefore, the get() call times out and throws TimeoutException.
Notice that a timeout does not automatically cancel the task. The task may continue running in the background.
7. Cancelling a Task
Sometimes we may decide that a task is no longer required.
Future provides a method called cancel().
future.cancel(true);
It attempts to cancel the task.
The method accepts a boolean argument:
truemeans interruption may be requested if the task is already running.falsemeans do not interrupt a task that has already started.
Cancellation is not a guarantee that running code will immediately stop. The task should cooperate with interruption.
We will explore cancellation and task failures in more detail in Class 33.
8. Important Future Methods
Let's quickly revise the methods we have learned.
| Method | Purpose |
|---|---|
get() |
Waits for the result and retrieves it. |
get(time, unit) |
Waits for the result up to a specified time. |
isDone() |
Checks whether the task has completed, failed, or been cancelled. |
cancel(mayInterrupt) |
Attempts to cancel the task. |
isCancelled() |
Checks whether the task was successfully cancelled. |
9. What Happens If a Callable Task Fails?
So far, we have seen tasks that complete successfully.
But what if a task throws an exception?
When a Callable task fails, the exception is captured by the Future.
When we call get(), Java throws an
ExecutionException.
The original exception is available through
getCause().
Let's look at a small example.
import java.util.concurrent.*;
public class FutureExceptionExample {
public static void main(String[] args) {
ExecutorService executorService =
Executors.newSingleThreadExecutor();
Future<Integer> future = executorService.submit(() -> {
throw new IllegalArgumentException("Invalid input");
});
try {
Integer result = future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
System.out.println(
"Task failed: " + e.getCause()
);
} finally {
executorService.shutdown();
}
}
}
The task throws IllegalArgumentException. However, when we retrieve the result using get(), we receive an ExecutionException.
We can inspect the original exception using
e.getCause().
We will study task failure handling in greater detail in a later class.
10. What We Learned Today
Future gives us a way to work with the result of a task that executes asynchronously.
We can retrieve its result, check its completion status, wait for a limited time, or attempt to cancel it.
The most important concept to remember is that
future.get() blocks the calling thread
until the result is available.
In the next class, we will learn how to execute tasks
at a particular time or repeatedly using
ScheduledExecutorService.
Test Your Understanding
Let's see how well you understood this class!
Question 1: What does a Future represent?
Question 2: Which method retrieves the result of a Future?
Question 3: What happens when we call future.get() before the task finishes?
Question 4: What does isDone() check?
Question 5: What happens if get() exceeds its specified time limit?
Class Summary
Let's quickly revise what we learned today.
- Future represents the result of an asynchronous task.
- ExecutorService.submit() returns a Future object.
get()retrieves the result and blocks until it is available.isDone()checks whether the task has reached a completed state.- Timed
get()allows us to wait for a limited time. cancel()attempts to cancel a task.- ExecutionException is used to report a failure when retrieving a failed task's result.
Remember: Future gives us a way to check on a submitted task and retrieve its result when it becomes available.
You have completed 31 out of 46 classes.