Callable 和 FutureTask 是 Java 异步计算的底层组合,Callable 支持返回值和受检异常,FutureTask 作为 Runnable 和 Future 的桥接实现,需显式启动才能执行,否则调用 get() 会永久阻塞。

Callable 和 FutureTask 是 Java 中获取异步计算结果的底层组合,但直接用它们不如 ExecutorService.submit() 简洁;除非你需要复用任务、手动控制执行时机,或在没有线程池的场景下启动单次异步计算。
为什么不用 Runnable 而必须用 Callable
Runnable 的 run() 方法无返回值、不能抛受检异常;而 Callable 的 call() 方法可以返回任意类型、能抛 Exception。这是带返回值的前提。
-
Callable比Runnable多一个泛型参数,声明你要返回什么类型 - 实现时必须重写
call(),不能写成run() - 若在
call()中 throwIOException,调用方通过Future.get()会收到ExecutionException包裹它
如何把 Callable 包装成 FutureTask 并执行
FutureTask 既是 Runnable(可被 Thread.start()),又是 Future(支持 get()、cancel())。它是桥接的关键。
- 构造:
new FutureTask,泛型必须和(new MyCallable()) Callable一致 - 启动方式有两种:
new Thread(task).start()(直接起线程),或交给Executor.execute(task) - 不要重复调用
task.run()或task.start()——FutureTask不是线程安全的可重入任务,重复执行会抛IllegalStateException -
get()是阻塞的,若任务未完成就调用,当前线程会挂起;加超时更稳妥:task.get(3, TimeUnit.SECONDS)
Callable<integer> task = () -> {
Thread.sleep(1000);
return 42;
};
FutureTask<integer> future = new FutureTask(task);
new Thread(future).start(); // 启动
System.out.println(future.get()); // 阻塞等待,输出 42</integer></integer>
常见错误:任务没执行就调用 get(),或忽略取消逻辑
FutureTask 默认不会自动执行,它只是个容器;你得显式触发(Thread.start() 或 execute())。很多人写了 new FutureTask(...) 就直接 get(),结果永远卡住。
- 检查
isDone()再get():避免盲目阻塞 - 调用
cancel(true)后,再get()会立即抛CancellationException - 如果
call()中有阻塞 I/O(如Socket.read()),仅cancel(true)不一定能中断它——需配合Thread.interrupt()和代码里响应中断(比如检查Thread.currentThread().isInterrupted()) - 别把同一个
FutureTask提交两次到线程池:第二次提交会失败,报RejectedExecutionException(因为任务状态已变为RUNNING或DONE)
真正麻烦的不是写法,而是状态管理:FutureTask 的生命周期(NEW → RUNNING → DONE/CANCELLED)全靠你自己盯。一旦涉及超时、重试、取消链路,很快就会滑向手动维护线程状态的泥潭——这时候该考虑换成 CompletableFuture 或封装好的异步工具类了。










