Boost API Performance with Java FutureTask: Parallel Data Aggregation Explained
This article explains how to use Java's FutureTask, Callable, and ExecutorService to run multiple API calls concurrently, aggregate their results, and dramatically reduce total response time compared with sequential execution, complete with code examples and performance analysis.
Introduction
When a page needs to retrieve dozens of user‑behavior metrics (likes, posts, messages, follows, etc.), making ten or more API calls sequentially can take half a minute, causing severe front‑end latency.
Multithreaded Concurrent Execution and Result Aggregation
The core classes used are Future , FutureTask and ExecutorService . FutureTask wraps a Callable (a task that returns a result) and implements Runnable, allowing it to be submitted to a thread pool.
FutureTask implements Future semantics, providing start, cancel, query and result retrieval.
Callable is a generic callback interface that returns a value, while Runnable does not.
FutureTask also implements Runnable , so it can be executed by a thread.
The three‑step concurrency toolkit in Java consists of state, queue and CAS.
State
/** The run state of this task, initially NEW. ... */
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
private Callable<V> callable;
private Object outcome;
private volatile Thread runner;
private volatile WaitNode waiters;The state transitions from NEW to terminal states such as NORMAL, EXCEPTIONAL, or CANCELLED.
run Method
The run method performs the following steps:
Set runner to the current thread.
Invoke callable.call() to execute the task.
Store the result in outcome (or store the exception).
Update state to a terminal state.
Wake up all waiting threads.
Clear references for GC.
If the state is INTERRUPTING, yield the CPU until interruption finishes.
public void run() {
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran) set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s);
}
}get Method
Future.get()blocks until the task reaches a terminal state and then returns the result or throws an exception.
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}The timed version adds a timeout and throws TimeoutException if the task does not finish in time.
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null) throw new NullPointerException();
int s = state;
if (s <= COMPLETING && (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}awaitDone
This method repeatedly checks the task state, enqueues the current thread in a Treiber stack ( WaitNode) if the task is still running, and parks the thread until it is unblocked or the timeout expires.
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); }
int s = state;
if (s > COMPLETING) {
if (q != null) q.thread = null;
return s;
} else if (s == COMPLETING) {
Thread.yield();
} else if (q == null) {
q = new WaitNode();
} else if (!queued) {
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
} else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) { removeWaiter(q); return state; }
LockSupport.parkNanos(this, nanos);
} else {
LockSupport.park(this);
}
}
}Queue
FutureTask uses a single‑linked list as a Treiber stack to hold waiting threads. Each node ( WaitNode) stores the thread reference and a link to the next node.
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}The stack head is the waiters field.
CAS Operations
CAS is used to update state, runner and waiters. Offsets are obtained via Unsafe in a static block.
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset;
private static final long runnerOffset;
private static final long waitersOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
stateOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("waiters"));
} catch (Exception e) { throw new Error(e); }
}Practical Exercise
A Spring Boot project demonstrates aggregating user‑behavior data with a custom MyFutureTask class that uses a ThreadPoolExecutor (core 8, max 20, queue 10, keep‑alive 30 s).
private static ExecutorService executor = new ThreadPoolExecutor(
8, 20, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(10),
new ThreadFactoryBuilder().setNameFormat("User_Async_FutureTask-%d").setDaemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy());Each metric is fetched via executor.submit(() -> userService.countXxx(userId)) and the results are obtained with Future.get(). The controller measures total execution time, showing a reduction from ~52 s (serial) to ~10 s (parallel).
Summary
Using FutureTask allows tasks to run asynchronously, block only when the result is needed, and safely aggregate results. After JDK 8, alternatives such as CompletableFuture, ExecutorCompletionService and ForkJoinTask provide similar functionality.
Follow‑up topics include advanced async APIs, performance tuning, and thread‑pool configuration.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Interview Crash Guide
Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.
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.
