completablefuture天然支持任务完成后的链式回调,无需自定义线程池或重写方法;它默认在前一任务结束的线程上执行后续逻辑,通过thenaccept/thenapply处理成功、exceptionally/handle统一捕获异常,避免阻塞调用。

Java 中用 CompletableFuture 触发完成通知最直接
不需要自定义线程池或重写方法,CompletableFuture 天然支持任务完成后的链式回调。它把“任务执行”和“结果处理”解耦,且默认在前一个任务结束的线程上执行后续逻辑(除非显式指定线程池)。
常见错误是直接在 thenAccept 里更新 UI 或操作非线程安全对象,结果崩溃或数据错乱。尤其在 Swing/JavaFX 场景下,必须手动切回 UI 线程。
- 成功回调用
thenAccept或thenApply;失败统一用exceptionally或handle捕获,不能只靠 try-catch 包裹 submit - 若需区分成功/失败路径,优先用
handle:它接收结果和异常两个参数,返回值可统一处理 - 避免在回调里阻塞,比如调用
get()或join()—— 这会抵消异步价值,还可能死锁
Spring 环境下用 @Async + AsyncResult 配合回调接口
当已有 Spring 的 @Async 基础设施时,不建议另起 CompletableFuture。更轻量的做法是让异步方法返回 AsyncResult<t></t>,再由调用方注册监听器。
但注意:@Async 方法返回值只能是 void 或 Future 子类,原生不支持回调参数。所以实际得包装一层:
- 定义回调接口如
TaskCallback<t></t>,含onSuccess(T)和onFailure(Exception) - 异步方法体内手动调用
callback.onSuccess(result),而不是依赖框架自动触发 - 别把回调对象传进
@Async方法参数 —— Spring 不保证其线程安全,容易被多个任务共享污染
回调 URL 场景下必须校验签名与幂等性
服务间 HTTP 回调(如支付、媒体处理)不是简单收个请求就完事。POST /callback 被恶意重放或重复推送时,业务状态会错乱。
微信支付、阿里云 MNS 等都强制要求验证回调体签名,并通过 out_trade_no 或 task_id 做幂等判断。漏掉任一环节,线上就可能产生资损或重复扣费。
- 签名验证必须在业务逻辑前完成,失败立即返回
400或协议规定的错误 XML/JSON - 幂等键建议用服务端生成的唯一 ID(如数据库主键或 Redis SETNX),不要依赖客户端传的字段
- 回调接口必须是幂等的:同一
task_id多次请求,只做一次状态更新,其余直接返回成功
Android/iOS 原生开发中回调容易忽略线程切换
Java/Kotlin 里用 Handler(Looper.getMainLooper()) 或 AndroidX 的 LifecycleScope.launchWhenStarted;iOS 用 DispatchQueue.main.async —— 这些不是锦上添花,而是硬性要求。
现象很典型:回调里更新 TextView 或刷新 UITableView,偶尔闪退报 CalledFromWrongThreadException 或 EXC_BAD_ACCESS。根本原因是异步任务在子线程完成,但 UI 组件只允许主线程访问。
- iOS 上用
dispatch_semaphore_wait实现同步等待是反模式,会卡住主线程,用户感知就是“卡死” - Android 中不要在
onPostExecute(已废弃)或runOnUiThread里做耗时操作,否则又制造新阻塞 - 真正安全的做法:回调只负责发消息(如
LiveData.postValue()、NotificationCenter.post),UI 层监听并响应
回调机制本身不复杂,难的是每种场景下线程模型、生命周期和错误传播路径都不一样。最容易被忽略的是:回调函数的执行上下文不是调用方决定的,而是由异步任务的结束位置决定的 —— 这一点不厘清,90% 的诡异 Bug 都由此而生。











