线程基础:Thread、Runnable、Callable 与 Future
Java 中"如何创建一个可执行任务"这件事,经历了从最原始到逐步解耦的演进。 本文按 Thread → Runnable → Callable → Future 的脉络,梳理它们各自解决什么问题、怎么用、以及局限在哪。
一、整体脉络
一个核心思想,贯穿四个演进阶段。
Java 中"创建一个可执行任务"这件事,经历了从 Thread → Runnable → Callable → Future 的演进:
Thread
线程本身(重量级,任务与线程耦合)
→
Runnable
任务抽象(无返回值、不能抛受检异常)
→
Callable
任务抽象(有返回值、可抛异常)
→
Future
异步结果的"凭证"(拿到未来才会有的结果)
核心思想:把"任务是什么"和"由谁执行、怎么执行"解耦。这是后来线程池、
CompletableFuture 的基础。
二、Thread
最底层、最重量的那一层。
Thread 是 JVM 中对操作系统线程的封装,是真正被 CPU 调度执行的最小单位。
用法
常见有两种使用方式:
- 把任务交给线程: new Thread(runnable).start();
- 继承 Thread 并重写 run(): 在子类中覆盖 run() 方法,再调用 start()。
注意
不要对同一个线程对象重复调用 start(),否则会抛出 IllegalThreadStateException。
三、Runnable
把"任务"从"线程"里拆出来的第一步。
Runnable 是一个函数式接口,实现它即可定义多线程任务。记得重写其中的 run() 方法:
@FunctionalInterface
public interface Runnable {
void run(); // 无返回值,不能抛受检异常
}Java
1. 使用方法
// 方式一:作为任务传给 Thread
Runnable r = () -> System.out.println("hello");
new Thread(r).start();
// 方式二:提交给线程池
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.execute(r);Java
2. 为什么使用 Runnable
- 解耦: 任务逻辑独立于线程,可复用、可测试。
- 避免单继承限制: Java 单继承,继承 Thread 就不能再继承别的类;实现接口则没有这个限制。
- 线程池友好: 它是线程池 execute() 的参数类型。
3. 局限
- 没有返回值 → 想知道任务算出了什么,只能靠共享变量 + 同步,很别扭。
- 不能抛受检异常 → 只能自己 try-catch 包起来。
- 无法知道任务是否完成、是否失败。
这两点直接催生了 Callable / Future。
四、Callable
"带返回值的 Runnable"。
1. 是什么
Callable 是带返回值的任务接口,与 Runnable 类似,但支持返回结果并抛出异常。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception; // 有返回值,可抛异常
}Java
2. 用法
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 42;
};
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> future = pool.submit(task); // 注意用 submit 而不是 execute
Integer result = future.get(); // 阻塞直到拿到结果Java
3. 何时用
- 任务需要返回结果(计算、查询、远程调用)。
- 任务可能抛异常,希望把异常传递给调用方。
- 需要配合 Future 做异步编排。
4. 注意
- Callable 只能提交给线程池(submit),不能直接 new Thread(callable) —— Thread 构造只接受 Runnable。
- 如果想用 Thread 跑 Callable,需要 FutureTask(见下节)。
五、Future
异步结果的"凭证"。
1. 是什么
Future 是异步计算结果的句柄(占位符)。提交任务后立即返回,未来某个时刻可以通过它拿结果。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException, ExecutionException;
}Java
2. 核心方法
| 方法 | 作用 |
|---|---|
| get() | 阻塞获取结果;任务未完成就一直等 |
| get(timeout, unit) | 带超时的等待,超时抛 TimeoutException |
| isDone() | 任务是否已结束(正常 / 异常 / 取消都算 done) |
| cancel(true) | 尝试取消;true 表示中断正在执行的线程 |
| isCancelled() | 是否被取消 |
3. 异常处理
- 任务内部抛异常 → 调用 get() 时抛 ExecutionException,用 getCause() 拿原始异常。
- 不调用 get(),异常会被静默吞掉——这是常见坑。
4. FutureTask
FutureTask 同时实现 Runnable 和 Future,是"可执行 + 可拿结果"的桥梁:
FutureTask<Integer> ft = new FutureTask<>(() -> 1 + 1);
new Thread(ft).start(); // 既能当 Runnable
Integer r = ft.get(); // 又能当 FutureJava
线程池 submit(Callable) 内部其实就是把 Callable 包成 FutureTask 再执行。
5. 局限(为什么后来有 CompletableFuture)
- get() 是阻塞的,无法"完成后自动回调"。
- 无法方便地组合多个 Future(链式、合并、任一完成)。
- 没有 thenApply / thenCompose / allOf 这类编排能力。
→ 这些由 CompletableFuture 解决。