宜兰县网站建设_网站建设公司_门户网站_seo优化
2026/1/21 16:03:27 网站建设 项目流程

知识点 5.1:并发编程进阶 —— Callable 与 Future

在学习了 Runnable 之后,我们很快会发现它的两个主要局限:

  1. run() 方法没有返回值。
  2. run() 方法不能抛出受检异常。

为了解决这两个问题,JUC 提供了一对更强大的组合:CallableFuture

1. 核心理论:Callable 接口

java.util.concurrent.Callable 是一个类似于 Runnable 的接口,但功能更强。

@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}

从源码可以看出它与 Runnable 的三大区别:

  1. 方法名不同: 一个是 call(),一个是 run()
  2. 有返回值: call() 方法可以返回一个泛型 V 的结果。
  3. 能抛出异常: 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();}
}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询