listenablefuture 更实用因其支持异步回调而非阻塞等待;需通过 listeningexecutorservice 创建,用 futures.addcallback 注册成功/失败处理,transform/catching 构建流水线,但 guava 28+ 起为 @beta,新项目宜优先选 completablefuture。

为什么 ListenableFuture 比原生 Future 更实用
因为原生 Future 只能阻塞等待结果或轮询状态,没法真正“监听”完成事件;ListenableFuture 在完成时主动触发回调,适合链式异步处理、避免线程空转。
常见错误现象:用 future.get() 等待多个任务,导致线程卡死或响应延迟飙升;或者手动起线程轮询 isDone(),浪费资源又难维护。
- 必须配合
ListeningExecutorService创建(不能直接 new),否则监听器不生效 -
MoreExecutors.directExecutor()适合轻量回调(如日志、简单状态更新),但别在里面做耗时操作,否则会阻塞调用线程 - 监听器执行线程由传入的
Executor决定,不是原始任务线程 —— 这点常被忽略,导致并发安全问题
怎么给异步任务加成功/失败回调
用 Futures.addCallback() 注册两个函数,分别处理成功和异常路径,比 try-catch + get() 更清晰、更不易漏错。
使用场景:HTTP 调用后刷新本地缓存、数据库写入失败时发告警、RPC 返回后组装 DTO。
立即学习“Java免费学习笔记(深入)”;
ListenableFuture<String> future = service.submitAsyncTask();
Futures.addCallback(future,
new FutureCallback<String>() {
public void onSuccess(String result) {
cache.put("key", result);
}
public void onFailure(Throwable t) {
logger.error("task failed", t);
}
},
executor);
-
onSuccess和onFailure不会同时触发,且保证只调用一次 - 如果回调里抛出异常,会被
executor的未捕获异常处理器吞掉(默认静默),建议在回调内包一层 try-catch - 不要在
onSuccess里再调get()—— 此时结果已确定,直接用参数值即可
Futures.transform() 和 Futures.catching() 怎么选
两者都返回新的 ListenableFuture,用于构建异步流水线:transform 处理正常结果转换,catching 专门兜底特定异常类型。
参数差异明显:transform 第二个参数是 AsyncFunction(返回新 ListenableFuture)或 Function(同步转换);catching 需显式指定异常类和 fallback 函数。
- 想把
ListenableFuture<httpresponse></httpresponse>转成ListenableFuture<user></user>?用transform+Function - 遇到
IOException时返回默认值,其他异常继续上抛?用catching,别用通用onFailure - 性能影响:每次
transform或catching都会包装一层 future,深层嵌套可能增加调度开销,但通常可忽略
Guava 28+ 中 ListenableFuture 的兼容性风险
Guava 28 开始标记 ListenableFuture 为 @Beta,且明确不承诺二进制兼容;Java 19+ 的虚拟线程(Project Loom)也未对其优化。生产环境升级 Guava 前必须验证。
容易踩的坑:用 CompletableFuture 替换时发现 transform 行为不一致(比如 null 处理)、或与旧版 Futures.allAsList() 返回类型冲突。
- 新项目优先考虑
CompletableFuture(JDK 8+ 原生支持,API 更丰富,Loom 兼容性好) - 老系统还在用 Guava 回调链,就别强行切到
thenApply—— 混用两种 future 容易引发线程模型混乱 -
Futures.successfulAsList()和Futures.inCompletionOrder()这类聚合工具,在 CompletableFuture 中没有直接等价物,迁移成本高
真正麻烦的不是语法,是回调执行时机和线程归属——尤其当底层服务从线程池切换到虚拟线程时,directExecutor() 的行为会变得不可控。










