Implementing Asynchronous Timeout with CompletableFuture in Java (JDK 8 & JDK 9)
This article explains how to use Java's CompletableFuture for parallel execution, demonstrates the limitations of simple timeout handling in JDK 8, and shows how JDK 9's built‑in orTimeout method and a custom utility class can provide precise asynchronous timeout control for backend services.
JDK 8 introduced CompletableFuture, enabling event‑driven asynchronous programming and allowing parallel execution of tasks such as multiple RPC calls. A simple serial example shows two compute calls executed one after another, taking at least four seconds.
By creating asynchronous tasks with CompletableFuture.supplyAsync and aggregating them with thenCombineAsync , the same work can be completed in roughly two seconds, as demonstrated by the parallel demo code.
public static void main(String[] args) {
// 仅简单举例,在生产代码中不推荐这样编写
// 统计耗时的函数
time(() -> {
CompletableFuture
result = Stream.of(1, 2)
// 创建异步任务
.map(x -> CompletableFuture.supplyAsync(() -> compute(x), executor))
// 聚合
.reduce(CompletableFuture.completedFuture(0), (x, y) -> x.thenCombineAsync(y, Integer::sum, executor));
// 等待结果
try {
System.out.println("结果:" + result.get());
} catch (ExecutionException | InterruptedException e) {
System.err.println("任务执行异常");
}
});
}
// 输出示例
// [async-1]: 任务执行开始:1
// [async-2]: 任务执行开始:2
// [async-1]: 任务执行完成:1
// [async-2]: 任务执行完成:2
// 结果:3
// 耗时:2 秒When tasks have unpredictable latency, a timeout is needed. Using Future.get(long, TimeUnit) allows each CompletableFuture to be bounded, but handling timeouts manually can be verbose, as shown in the following example.
public static void main(String[] args) {
// 仅简单举例,在生产代码中不推荐这样编写
time(() -> {
List
> result = Stream.of(1, 2)
.map(x -> CompletableFuture.supplyAsync(() -> compute(x), executor))
.toList();
int res = 0;
for (CompletableFuture
future : result) {
try {
res += future.get(2, SECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
System.err.println("任务执行异常或超时");
}
}
System.out.println("结果:" + res);
});
}
// 输出示例
// [async-2]: 任务执行开始:2
// [async-1]: 任务执行开始:1
// [async-1]: 任务执行完成:1
// 任务执行异常或超时
// 结果:1
// 耗时:2 秒JDK 9 added native timeout support with orTimeout and completeOnTimeout . The implementation creates a scheduled task that throws a TimeoutException if the original future is not completed in time, and cancels the timer when the future finishes.
public CompletableFuture
orTimeout(long timeout, TimeUnit unit) {
if (unit == null) throw new NullPointerException();
if (result == null)
whenComplete(new Canceller(Delayer.delay(new Timeout(this), timeout, unit)));
return this;
}
static final class Timeout implements Runnable {
final CompletableFuture
f;
Timeout(CompletableFuture
f) { this.f = f; }
public void run() {
if (f != null && !f.isDone())
f.completeExceptionally(new TimeoutException());
}
}
static final class Delayer {
static ScheduledFuture
delay(Runnable command, long delay, TimeUnit unit) {
return delayer.schedule(command, delay, unit);
}
static final class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("CompletableFutureDelayScheduler");
return t;
}
}
static final ScheduledThreadPoolExecutor delayer;
static {
delayer = new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory());
delayer.setRemoveOnCancelPolicy(true);
}
}
static final class Canceller implements BiConsumer
{
final Future
f;
Canceller(Future
f) { this.f = f; }
public void accept(Object ignore, Throwable ex) {
if (ex == null && f != null && !f.isDone())
f.cancel(false);
}
}For projects still on JDK 8, a compatible utility can be built using the same principles. The provided CompletableFutureExpandUtils class offers an orTimeout method that schedules a timeout task and cancels it when the future completes, mimicking JDK 9 behavior.
package com.jd.jr.market.reduction.util;
import com.jdpay.market.common.exception.UncheckedException;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
/**
* CompletableFuture 扩展工具
*/
public class CompletableFutureExpandUtils {
public static
CompletableFuture
orTimeout(CompletableFuture
future, long timeout, TimeUnit unit) {
if (unit == null) throw new UncheckedException("时间的给定粒度不能为空");
if (future == null) throw new UncheckedException("异步任务不能为空");
if (future.isDone()) return future;
return future.whenComplete(new Canceller(Delayer.delay(new Timeout(future), timeout, unit)));
}
static final class Timeout implements Runnable {
final CompletableFuture
future;
Timeout(CompletableFuture
future) { this.future = future; }
public void run() {
if (future != null && !future.isDone())
future.completeExceptionally(new TimeoutException());
}
}
static final class Canceller implements BiConsumer
{
final Future
future;
Canceller(Future
future) { this.future = future; }
public void accept(Object ignore, Throwable ex) {
if (ex == null && future != null && !future.isDone())
future.cancel(false);
}
}
static final class Delayer {
static ScheduledFuture
delay(Runnable command, long delay, TimeUnit unit) {
return delayer.schedule(command, delay, unit);
}
static final class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("CompletableFutureExpandUtilsDelayScheduler");
return t;
}
}
static final ScheduledThreadPoolExecutor delayer;
static {
delayer = new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory());
delayer.setRemoveOnCancelPolicy(true);
}
}
}In summary, while JDK 8 requires custom code to achieve reliable asynchronous timeout, JDK 9 and later provide built‑in methods that simplify the implementation and improve precision, making CompletableFuture a robust tool for backend concurrency control.
JD Tech
Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.
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.