Java并发:Callable与Future详解
Callable是能返回值的异步任务,Future是其结果的凭证。将Callable提交给线程池,立即返回Future,主线程无需等待,可在未来通过它获取任务的执行结果。
我们来详细、清晰地讲解一下 Java 中的 Future 和 Callable。
核心思想:异步计算与结果获取
想象一个场景:你去咖啡店点了一杯手冲咖啡。
- 下单(提交任务): 你告诉店员(线程池)你要一杯咖啡(一个耗时的任务)。
- 拿到小票(获取凭证): 店员不会让你站在原地干等,而是给你一张小票(
Future)。这张小票就是你未来能取到咖啡的凭证。 - 做自己的事(主线程不阻塞): 拿到小票后,你可以去玩手机、看书,而不是傻傻地盯着咖啡师操作。你的主程序可以继续执行其他代码。
- 取咖啡(获取结果): 等到咖啡做好了,店员会叫号。你凭着小票去取咖啡。如果你去早了,就得在取餐口等着(
Future.get()阻塞);如果去的时候正好做好,就直接拿走。
在这个比喻中:
- 制作咖啡的任务 ->
Callable - 那张小票 ->
Future - 咖啡店和店员 ->
ExecutorService(线程池)
现在,我们来看一下这两个接口在 Java 中的具体定义和用法。
1. Callable<V> 接口
Callable 是一个接口,它代表一个有返回结果并且可能抛出异常的任务。
它与我们更熟悉的 Runnable 接口非常相似,但有两大关键区别:
| 特性 | Runnable |
Callable<V> |
|---|---|---|
| 核心方法 | void run() |
V call() |
| 返回值 | 没有 (void) |
有,类型为泛型 V |
| 异常 | 不能抛出受检异常 | 可以抛出受检异常 (throws Exception) |
Callable 接口定义
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable 代码示例
创建一个 Callable 任务,比如计算 1到100 的和。
import java.util.concurrent.Callable;
// <Integer> 表示这个任务执行完毕后会返回一个 Integer 类型的结果
public class SumTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子线程开始计算...");
Thread.sleep(2000); // 模拟耗时计算
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println("子线程计算完成!");
return sum;
}
}
2. Future<V> 接口
Future 接口代表一个异步计算的未来结果。它是一个占位符,你可以在任务执行完成后通过它来获取最终的结果。
Future 提供了几个核心方法来与这个“未来的结果”进行交互:
V get(): 阻塞式获取结果。如果任务已经完成,它会立即返回结果。如果任务还没有完成,调用这个方法的线程将会被阻塞,直到任务完成并返回结果。V get(long timeout, TimeUnit unit): 带超时的阻塞式获取。如果在指定时间内任务仍未完成,它会抛出TimeoutException。boolean isDone(): 判断任务是否已经完成(无论是正常完成、异常退出还是被取消)。这个方法是非阻塞的,可以用来轮询任务状态。boolean isCancelled(): 判断任务在完成前是否被取消了。boolean cancel(boolean mayInterruptIfRunning): 尝试取消任务的执行。- 如果任务还没开始,它就不会被执行。
- 如果任务已经开始,
mayInterruptIfRunning参数决定是否要中断执行该任务的线程。
3. 如何将 Callable 和 Future 结合使用?
Callable 只是一个任务定义,它自己无法执行。你需要将它提交给一个 ExecutorService (线程池) 来执行。当你提交一个 Callable 任务时,ExecutorService 会立即返回一个 Future 对象。
完整的使用流程和代码示例
import java.util.concurrent.*;
public class FutureCallableExample {
public static void main(String[] args) {
// 1. 创建一个线程池
// Executors 是一个工具类,可以方便地创建各种线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 2. 创建一个 Callable 任务实例
Callable<Integer> sumTask = new SumTask();
// 3. 提交任务到线程池,并获取 Future 对象
// submit() 方法会立即返回一个 Future,不会阻塞
System.out.println("主线程:提交计算任务...");
Future<Integer> futureResult = executorService.submit(sumTask);
// 4. 主线程可以继续做其他事情
System.out.println("主线程:任务已提交,我先去做点别的事情...");
try {
// 模拟主线程的其他工作
Thread.sleep(1000);
System.out.println("主线程:其他事情做完了。");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5. 在需要的时候,获取异步任务的结果
System.out.println("主线程:现在我需要计算结果了。");
try {
// 调用 future.get() 来获取结果
// 如果此时子线程的任务还没执行完,主线程会在这里阻塞等待
Integer result = futureResult.get(); // 阻塞点
System.out.println("主线程:获取到任务结果 -> " + result);
} catch (InterruptedException e) {
// 当前线程在等待过程中被中断
e.printStackTrace();
} catch (ExecutionException e) {
// 任务在执行过程中抛出了异常
System.err.println("任务执行出错!");
e.printStackTrace();
}
// 6. 关闭线程池
// 这非常重要,否则 JVM 不会退出
executorService.shutdown();
}
}
// SumTask 类 (与上面的示例相同)
class SumTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子线程开始计算...");
Thread.sleep(2000); // 模拟耗时计算
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println("子线程计算完成!");
return sum;
}
}
运行结果分析:
主线程:提交计算任务...
主线程:任务已提交,我先去做点别的事情...
子线程开始计算...
主线程:其他事情做完了。
主线程:现在我需要计算结果了。
// (主线程在这里会等待大约1秒,因为子线程需要2秒,主线程已经消耗了1秒)
子线程计算完成!
主-线程:获取到任务结果 -> 5050
总结
| 组件 | 角色 | 作用 |
|---|---|---|
Callable |
任务本身 📝 | 定义了一个有返回值、能抛异常的异步任务。它描述了“要做什么”。 |
Future |
结果凭证 🎟️ | 代表异步任务的未来结果。提供方法来检查任务状态、取消任务以及获取结果。 |
ExecutorService |
执行者 👨🍳 | 接收 Callable 任务,分配线程去执行它,并立即返回一个 Future 对象。 |
核心关系:你将一个 Callable(任务)交给 ExecutorService(执行者),它返回给你一个 Future(凭证)。你拿着这个 Future 凭证,在未来的某个时间点去获取任务的执行结果。
这种模式是 Java 并发编程中处理异步任务并获取其结果的基础,非常强大和常用,尤其是在需要执行耗时操作(如网络请求、数据库查询、复杂计算)而不希望阻塞主线程的场景中。
进阶:CompletableFuture
在 Java 8 中,引入了功能更强大的 CompletableFuture。它对 Future 进行了极大的增强,支持非阻塞式的回调、链式调用、组合多个 Future 等,是现代 Java 异步编程的首选。如果你已经掌握了 Future 和 Callable,下一步强烈建议学习 CompletableFuture。