Java中Callable 、Future 和 Runnable 的区别?
在 Java 并发编程中,Runnable、Callable 和 Future 是三个非常核心的接口。理解它们的区别和联系,是掌握 Java 多线程的关键。
简单来说:Runnable 和 Callable 是定义“任务”的接口,而 Future 是用来获取“任务执行结果”的句柄(凭证)。
以下是它们的详细区别与解析:
1. Runnable(没有返回值的任务)
- 诞生时间:JDK 1.0 引入,是最古老的多线程接口。
- 核心方法:
void run() - 特点:
- 无返回值:任务执行完了就完了,调用者无法直接获取执行结果。
- 不能抛出受检异常:
run()方法的签名没有throws,如果任务内部发生异常,必须在内部try-catch消化掉,不能向外抛出。
- 执行方式:可以直接通过
new Thread(runnable).start()执行,也可以提交给线程池(ExecutorService)执行。
2. Callable(有返回值的任务)
- 诞生时间:JDK 1.5 引入(位于
java.util.concurrent包下),是为了弥补Runnable的不足而设计的。 - 核心方法:
V call() throws Exception - 特点:
- 有返回值:支持泛型,任务执行完毕后可以返回一个具体的值。
- 能抛出异常:允许向上层抛出受检异常(Checked Exception),调用者可以捕获并处理。
- 执行方式:不能直接传给
Thread执行,必须提交给线程池(ExecutorService.submit())执行,或者封装成FutureTask后再交给线程执行。
3. Future(异步结果的凭证)
- 概念:
Future本身不是任务,它是用来表示异步计算结果的接口。 - 作用:当你把一个
Callable任务提交给线程池后,线程池会立刻返回一个Future对象。你可以拿着这个Future去做别的事,稍后再通过它来获取Callable任务的执行结果。 - 核心方法:
V get():阻塞获取任务结果。如果任务没执行完,调用此方法的线程会被阻塞,直到任务完成。V get(long timeout, TimeUnit unit):超时获取。如果在指定时间内没拿到结果,就抛出超时异常。boolean isDone():判断任务是否已经执行完成。boolean cancel(boolean mayInterruptIfRunning):尝试取消任务。
核心对比总结
Runnable vs Callable
| 对比维度 | Runnable |
Callable<V> |
|---|---|---|
| 包路径 | java.lang |
java.util.concurrent |
| 引入版本 | JDK 1.0 | JDK 1.5 |
| 核心方法 | void run() |
V call() |
| 返回值 | 无 (void) |
有 (泛型 V) |
| 异常处理 | 不能抛出受检异常,必须内部消化 | 可以抛出 Exception,由调用者处理 |
| 配合 Thread | 可以直接 new Thread(runnable) |
不能直接配合 Thread 使用 |
Callable 和 Future 的关系(黄金搭档)
Callable 负责产生结果,Future 负责获取结果。它们通常结合线程池一起使用:Future<V> future = executorService.submit(Callable<V> task);
(注:executorService.submit() 也可以接收 Runnable,也会返回一个 Future,但是调用这个 Future.get() 时,任务执行完毕后只会返回 null。)
代码演示
下面的代码直观地展示了三者的用法:
java
import java.util.concurrent.*;
public class ConcurrencyDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 1. Runnable 任务
Runnable runnableTask = () -> {
System.out.println("Runnable: 正在执行,没有返回值...");
// 如果这里有异常,只能 try-catch,不能抛出
};
// 2. Callable 任务
Callable<String> callableTask = () -> {
System.out.println("Callable: 正在执行复杂的计算...");
Thread.sleep(2000); // 模拟耗时操作
return "这是 Callable 的计算结果!"; // 有返回值
// 这里可以直接 throws Exception
};
// 提交 Runnable (不关心结果)
executor.execute(runnableTask);
// 提交 Callable,并获取 Future 凭证
Future<String> future = executor.submit(callableTask);
System.out.println("主线程: 任务已提交,主线程可以去干别的事情...");
// 3. 使用 Future 获取结果
System.out.println("主线程: 准备获取 Callable 的结果...");
// future.get() 会阻塞主线程,直到 Callable 任务执行完毕并返回结果
String result = future.get();
System.out.println("主线程: 拿到结果 -> " + result);
// 关闭线程池
executor.shutdown();
}
}
补充进阶:FutureTask
如果你不想用线程池,又想拿到 Callable 的结果,可以使用 FutureTask。FutureTask 是一个非常精妙的类,它同时实现了 Runnable 和 Future 接口。它既可以作为任务被 Thread 执行,又可以作为凭证调用 get() 获取结果。
java
Callable<Integer> callable = () -> { return 100; };
// 将 Callable 包装成 FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 因为 FutureTask 实现了 Runnable,所以可以传给 Thread
new Thread(futureTask).start();
// 因为 FutureTask 实现了 Future,所以可以 get 获取结果
Integer result = futureTask.get();
右滑查看面试常问