Mastering Java Async: Future, Callable, CompletableFuture, and FutureTask Explained
This article explores four Java asynchronous programming approaches—Future with Callable, CompletableFuture, and FutureTask—detailing their principles, usage patterns, code examples, and how to retrieve results from thread pools, helping developers improve performance for tasks like aggregating order and fee summaries.
We have a business scenario similar to a report that gathers user order summaries, shipping fee summaries, and various transaction fee summaries, then displays them on a page. The data sources are independent, and fetching them sequentially is slow.
To speed up the page, we can fetch each summary in parallel using multiple threads and combine the results. In Java, asynchronous thread results are typically obtained with
Future,
Callable,
CompletableFuture, and
FutureTaskclasses.
Using Future and Callable
<code>package com.luke.designpatterns.demo;
import java.util.concurrent.*;
public class demo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new Callable<Integer>() {
public Integer call() throws Exception {
// Get various summaries and return result
return 42;
}
});
// Get asynchronous task result
Integer result = future.get();
System.out.println("异步任务的结果是" + result);
executor.shutdown();
}
}
</code>The principle is to submit a task to a thread pool, which returns a
Futureobject that can later retrieve the task's result.
Callable interface: A generic interface that defines a
call()method to implement a task returning a result and possibly throwing an exception.
Future interface: Represents the result of an asynchronous computation, providing methods like
get()that block until the computation completes and returns the result.
The Callable interface itself does not start a thread; it defines a task that must be submitted to an ExecutorService thread pool for execution.
In
ExecutorService, you can use
submit(Callable<T> task)to submit a
Callabletask, which returns a
Futurefor result retrieval.
Steps to start a Callable task:
Create a
Callableinstance by implementing the
call()method with the desired logic.
Create an
ExecutorServicethread pool using factory methods such as
Executors.newFixedThreadPool(int)or
Executors.newCachedThreadPool().
Submit the
Callableinstance to the thread pool via
submit(Callable<T> task).
The thread pool executes the task asynchronously in a separate thread.
Obtain the result through the
Future's
get()method, which blocks until the task completes.
Overall,
Callableworks by submitting a task to an
ExecutorService, which manages its execution in a separate thread, allowing asynchronous processing.
Using CompletableFuture
<code>import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
// Get various summaries and return result
return 43;
});
// Get asynchronous task result
Integer result = future.get();
System.out.println("异步任务的结果:" + result);
}
}
</code>CompletableFuture(introduced in Java 8) enables asynchronous programming and task composition based on the concepts of a “completable” future.
The core principles of
CompletableFutureare:
Asynchronous execution: Methods like
supplyAsync()and
runAsync()submit tasks that run in separate threads without blocking the main thread.
Callback mechanism: Methods such as
thenApply(),
thenAccept(), and
thenRun()register callbacks to handle results, completion actions, or exceptions.
Task composition: Methods like
thenCombine(),
thenCompose(), and
thenAcceptBoth()allow combining multiple tasks and defining dependencies.
Exception handling: Methods like
exceptionally()and
handle()provide ways to process exceptions thrown during task execution.
Waiting for completion: Similar to
Future,
CompletableFutureoffers a
get()method, but it does not block the calling thread because the task runs asynchronously.
In summary,
CompletableFutureleverages callbacks and asynchronous execution to simplify handling of async tasks, result processing, composition, and error handling.
Using FutureTask
<code>import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
// Get various summaries and return result
return 44;
});
Thread thread = new Thread(futureTask);
thread.start();
// Get asynchronous task result
Integer result = futureTask.get();
System.out.println("异步任务的结果:" + result);
}
}
</code>FutureTaskis a concrete implementation of the
Futureinterface that also implements
Runnable, allowing it to be executed by a thread or an executor.
The principle of
FutureTaskcan be summarized as:
Encapsulate task: Accepts a
Callableor
Runnableand wraps it as an asynchronous task.
Execute task: Implements
Runnable, so it can be submitted to an
Executor(typically an
ExecutorService) for execution in a separate thread.
Get result: Through the
Futureinterface's
get()method, it waits for task completion and returns the result, blocking if necessary.
Cancel task: Provides
cancel(boolean mayInterruptIfRunning)to attempt cancellation; if cancelled, subsequent
get()throws
CancellationException.
Overall,
FutureTaskwraps a callable or runnable into a cancellable asynchronous task and uses the
Futureinterface to retrieve results or handle cancellation.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.