基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Java并发:Callable与Future详解

知识点图片

Callable是能返回值的异步任务,Future是其结果的凭证。将Callable提交给线程池,立即返回Future,主线程无需等待,可在未来通过它获取任务的执行结果。

我们来详细、清晰地讲解一下 Java 中的 FutureCallable

核心思想:异步计算与结果获取

想象一个场景:你去咖啡店点了一杯手冲咖啡。

  1. 下单(提交任务): 你告诉店员(线程池)你要一杯咖啡(一个耗时的任务)。
  2. 拿到小票(获取凭证): 店员不会让你站在原地干等,而是给你一张小票(Future)。这张小票就是你未来能取到咖啡的凭证。
  3. 做自己的事(主线程不阻塞): 拿到小票后,你可以去玩手机、看书,而不是傻傻地盯着咖啡师操作。你的主程序可以继续执行其他代码。
  4. 取咖啡(获取结果): 等到咖啡做好了,店员会叫号。你凭着小票去取咖啡。如果你去早了,就得在取餐口等着(Future.get() 阻塞);如果去的时候正好做好,就直接拿走。

在这个比喻中:

  • 制作咖啡的任务 -> Callable
  • 那张小票 -> Future
  • 咖啡店和店员 -> ExecutorService (线程池)

现在,我们来看一下这两个接口在 Java 中的具体定义和用法。


1. Callable<V> 接口

Callable 是一个接口,它代表一个有返回结果并且可能抛出异常的任务。

它与我们更熟悉的 Runnable 接口非常相似,但有两大关键区别:

特性 Runnable Callable<V>
核心方法 void run() V call()
返回值 没有 (void) 有,类型为泛型 V
异常 不能抛出受检异常 可以抛出受检异常 (throws Exception)

Callable 接口定义

java
@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 的和。

java
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. 如何将 CallableFuture 结合使用?

Callable 只是一个任务定义,它自己无法执行。你需要将它提交给一个 ExecutorService (线程池) 来执行。当你提交一个 Callable 任务时,ExecutorService 会立即返回一个 Future 对象。

完整的使用流程和代码示例

java
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;
    }
}

运行结果分析:

plaintext
主线程:提交计算任务...
主线程:任务已提交,我先去做点别的事情...
子线程开始计算...
主线程:其他事情做完了。
主线程:现在我需要计算结果了。
// (主线程在这里会等待大约1秒,因为子线程需要2秒,主线程已经消耗了1秒)
子线程计算完成!
主-线程:获取到任务结果 -> 5050

总结

组件 角色 作用
Callable 任务本身 📝 定义了一个有返回值、能抛异常的异步任务。它描述了“要做什么”。
Future 结果凭证 🎟️ 代表异步任务的未来结果。提供方法来检查任务状态、取消任务以及获取结果。
ExecutorService 执行者 👨‍🍳 接收 Callable 任务,分配线程去执行它,并立即返回一个 Future 对象。

核心关系:你将一个 Callable(任务)交给 ExecutorService(执行者),它返回给你一个 Future(凭证)。你拿着这个 Future 凭证,在未来的某个时间点去获取任务的执行结果。

这种模式是 Java 并发编程中处理异步任务并获取其结果的基础,非常强大和常用,尤其是在需要执行耗时操作(如网络请求、数据库查询、复杂计算)而不希望阻塞主线程的场景中。

进阶:CompletableFuture

在 Java 8 中,引入了功能更强大的 CompletableFuture。它对 Future 进行了极大的增强,支持非阻塞式的回调、链式调用、组合多个 Future 等,是现代 Java 异步编程的首选。如果你已经掌握了 FutureCallable,下一步强烈建议学习 CompletableFuture

00:00
00:00