基于本文回答
0
评论

Java中Callable 、Future 和 Runnable 的区别?

知识点图片

在 Java 并发编程中,RunnableCallableFuture 是三个非常核心的接口。理解它们的区别和联系,是掌握 Java 多线程的关键。

简单来说:RunnableCallable 是定义“任务”的接口,而 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 是一个非常精妙的类,它同时实现了 RunnableFuture 接口。它既可以作为任务被 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(); 
右滑查看面试常问