Backend Development 12 min read

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.

macrozheng
macrozheng
macrozheng
Mastering Java Async: Future, Callable, CompletableFuture, and FutureTask Explained

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

FutureTask

classes.

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

Future

object 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&lt;T&gt; task)

to submit a

Callable

task, which returns a

Future

for result retrieval.

Steps to start a Callable task:

Create a

Callable

instance by implementing the

call()

method with the desired logic.

Create an

ExecutorService

thread pool using factory methods such as

Executors.newFixedThreadPool(int)

or

Executors.newCachedThreadPool()

.

Submit the

Callable

instance to the thread pool via

submit(Callable&lt;T&gt; 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,

Callable

works 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

CompletableFuture

are:

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

,

CompletableFuture

offers a

get()

method, but it does not block the calling thread because the task runs asynchronously.

In summary,

CompletableFuture

leverages 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>
FutureTask

is a concrete implementation of the

Future

interface that also implements

Runnable

, allowing it to be executed by a thread or an executor.

The principle of

FutureTask

can be summarized as:

Encapsulate task: Accepts a

Callable

or

Runnable

and 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

Future

interface'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,

FutureTask

wraps a callable or runnable into a cancellable asynchronous task and uses the

Future

interface to retrieve results or handle cancellation.

JavaConcurrencyasynchronousCompletableFutureCallableFutureFutureTask
macrozheng
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.