知识点 5.1:并发编程进阶 —— Callable 与 Future
在学习了 Runnable 之后,我们很快会发现它的两个主要局限:
run()方法没有返回值。run()方法不能抛出受检异常。
为了解决这两个问题,JUC 提供了一对更强大的组合:Callable 和 Future。
1. 核心理论:Callable 接口
java.util.concurrent.Callable 是一个类似于 Runnable 的接口,但功能更强。
@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}
从源码可以看出它与 Runnable 的三大区别:
- 方法名不同: 一个是
call(),一个是run()。 - 有返回值:
call()方法可以返回一个泛型V的结果。 - 能抛出异常:
call()方法的签名上声明了throws Exception,这意味着你可以在任务中抛出受检异常。
2. 深度剖析:Future —— 未来的“提货单”
既然 Callable 能返回结果,那我们怎么获取这个结果呢?
直接用一个变量去接 call() 的返回值是行不通的,因为 call() 是在另一个线程中异步执行的。主线程提交任务后,不会傻傻地一直等着,它会继续做自己的事。
为了解决这个问题,JUC 设计了 Future 接口。当你把一个 Callable 任务提交给线程池时,线程池会立刻返回一个 Future 对象。这个 Future 对象就像一张“提货单”或“承诺书”。
Future 接口的核心方法:
-
V get(): 这是一个阻塞方法。当主线程调用它时:- 如果任务已经执行完毕,它会立刻返回
Callable的执行结果。 - 如果任务还在执行中,主线程会阻塞在这里,一直等到任务执行完毕再返回结果。
- 如果任务在执行过程中抛出了异常,那么
get()方法会把那个异常原封不动地再次抛出来。
- 如果任务已经执行完毕,它会立刻返回
-
V get(long timeout, TimeUnit unit): 带超时的get()。主线程最多只阻塞指定的时间。如果超时后任务还没完成,会抛出TimeoutException。 -
boolean isDone(): 判断任务是否已经执行完成(无论是正常完成、异常终止还是被取消)。这个方法是非阻塞的,常用于轮询。 -
boolean cancel(boolean mayInterruptIfRunning): 尝试取消任务的执行。
3. 生活中的例子与代码示例
-
生活比喻: 你去一家高级咖啡店点了一杯手冲咖啡(一个耗时的
Callable任务)。- 你下单后,店员不会让你站在原地干等,而是会给你一个取餐牌(
Future对象),然后你就可以回到座位上玩手机了(主线程继续执行)。 - 你随时可以看一眼取餐牌上的状态灯(调用
isDone()),看看咖啡好了没。 - 当你觉得差不多了,就拿着取餐牌去柜台取咖啡(调用
get())。如果咖啡师正在做,你就得在柜台前等一会儿(get()阻塞)。如果咖啡做好了,你就能拿到咖啡(获取返回值)。如果制作过程中咖啡豆用完了(任务抛出异常),店员会告诉你这个坏消息(get()抛出异常)。
- 你下单后,店员不会让你站在原地干等,而是会给你一个取餐牌(
-
核心代码示例:
package com.study.concurrency;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class CallableAndFutureExample {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(2);// 传统方式:创建一个实现了 Callable 接口的类Callable<String> traditionalTask = new Callable<String>() {@Overridepublic String call() throws Exception {Thread.sleep(1000);return "传统方式执行完毕";}};Future<String> future1 = executor.submit(traditionalTask);// Lambda 方式 (Java 8+ 推荐)// 因为 Callable 也是函数式接口,所以可以直接用 Lambda 表达式Callable<String> lambdaTask = () -> {Thread.sleep(2000);// 模拟抛出异常if (true) { // a conditionthrow new IllegalStateException("Lambda 任务出错了");}return "Lambda 方式执行完毕";};Future<String> future2 = executor.submit(lambdaTask);System.out.println("主线程:已提交2个任务。");try {System.out.println("获取传统任务结果: " + future1.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}try {System.out.println("获取 Lambda 任务结果: " + future2.get());} catch (InterruptedException | ExecutionException e) {// 注意:原始异常被包装在 ExecutionException 中System.err.println("获取 Lambda 任务结果时出错: " + e.getCause());}executor.shutdown();}
}