Callable 是能返回结果并抛出受检异常的函数式接口,必须配合 ExecutorService 使用,不能直接传给 Thread;其返回值由泛型指定,通过 Future.get() 获取结果,但会阻塞线程。

Callable 是什么,为什么不能直接用 Runnable
Callable 是一个函数式接口,和 Runnable 类似,但关键区别在于它能返回结果、抛出受检异常。Runnable.run() 返回 void,而 Callable.call() 返回泛型类型 V,比如 Integer 或 String。如果你需要异步执行后拿回计算结果(比如查数据库、调第三方 API),Runnable 就不够用了。
常见错误现象:有人试图把 Callable 直接传给 Thread 构造器,会编译失败——因为 Thread 只接受 Runnable。
-
Callable必须配合ExecutorService使用,不能单独启动 - 它的
call()方法可以声明 throwsException,比run()更适合封装可能出错的逻辑 - 返回值类型由泛型决定,例如
Callable表示异步任务最终返回一个Boolean
Future 是怎么拿到 Callable 执行结果的
Future 不是任务本身,而是任务的「句柄」或「承诺」——你提交 Callable 后,ExecutorService.submit() 立即返回一个 Future 实例,它代表尚未完成的计算结果。
关键点在于:调用 Future.get() 会**阻塞当前线程**,直到任务完成并返回结果;如果任务抛异常,get() 会包装成 ExecutionException 抛出。
立即学习“Java免费学习笔记(深入)”;
ExecutorService executor = Executors.newFixedThreadPool(2); Futurefuture = executor.submit(() -> { Thread.sleep(1000); return 42; }); // 此处会阻塞,直到上面的 lambda 执行完 Integer result = future.get(); // 得到 42 executor.shutdown();
-
future.isDone()可轮询判断是否完成,避免盲等 -
future.get(3, TimeUnit.SECONDS)支持超时,超时会抛TimeoutException -
future.cancel(true)尝试中断正在运行的任务(前提是任务响应中断)
Future 的局限性:为什么经常要搭配 CompletableFuture
原生 Future 接口只有 get()、isDone()、cancel() 这几个方法,不支持链式处理、组合多个异步任务、或者非阻塞回调——这就是为什么 JDK 8 引入了 CompletableFuture。
典型问题场景:你想“查用户 → 再查该用户的订单 → 最后汇总统计”,用原始 Future 写出来就是层层嵌套 get(),线程被卡住,吞吐量暴跌。
-
CompletableFuture实现了Future和CompletionStage,可实现真正的异步流水线 -
thenApply()、thenCompose()、exceptionally()都是非阻塞的 - 它默认使用
ForkJoinPool.commonPool(),但也可以显式指定Executor避免打满公共池
别忽略的细节:线程池关闭与资源泄漏
很多人只关注任务怎么提交、结果怎么取,却忘了 ExecutorService 本身不是用完自动销毁的。如果不调用 shutdown() 或 shutdownNow(),JVM 无法退出(因为线程池里的工作线程还活着)。
更隐蔽的问题是:任务中若持有数据库连接、文件句柄或网络流,而 Callable.call() 没有正确 try-finally 释放,这些资源会在异步上下文中泄漏,且很难排查。
- 推荐用
try-with-resources包裹可关闭资源,哪怕在Callable中也生效 - 在线程池关闭前,应确保所有
Future已完成或取消,否则shutdown()后新任务会被拒绝 -
Future.get()抛出的ExecutionException的getCause()才是原始异常,别直接打印ExecutionException本身











