三分钟掌握 Runnable 和 Callable 异同

在 Java 多线程编程中,RunnableCallable 是定义任务的两种核心接口。它们的区别如下表:

特性 Runnable Callable
包路径 java.lang java.util.concurrent
方法签名 void run() V call() throws Exception
返回值 无返回值(void 可返回泛型类型结果(V
异常处理 不能抛出受检异常(需内部处理) 可抛出受检异常(由调用者处理)
使用场景 简单异步任务 需要结果返回或异常传递的复杂任务
线程池提交方式 execute(Runnable) submit(Callable) → 返回 Future
执行终止方式 仅通过中断标志位判断 可通过 Future.cancel() 终止
Java 版本 JDK 1.0+ JDK 1.5+

代码示例对比

1. Runnable 典型用法

Runnable task = new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println("执行任务");
        } catch (Exception e) {
            // 必须在此处理异常
        }
    }
};

// 线程池执行
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(task);

2. Callable 典型用法

Callable<Integer> task = new Callable<>() {
    @Override
    public Integer call() throws Exception {
        if (Math.random() > 0.5) {
            throw new IOException("模拟异常");
        }
        return 42;
    }
};

// 提交任务并获取Future
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);

try {
    Integer result = future.get(); // 阻塞获取结果
    System.out.println("结果:" + result);
} catch (ExecutionException e) {
    System.err.println("任务异常: " + e.getCause());
}

核心使用建议

  1. 选择时机

    • 需要任务结果 → Callable
    • 需要抛出异常 → Callable
    • 简单异步操作 → Runnable
  2. 返回值扩展

    // Runnable的变通方案(通过共享变量)
    AtomicInteger result = new AtomicInteger();
    Runnable task = () -> result.set(100);
    
  3. FutureTask 转换

    // 将Runnable包装为Callable
    Callable<Void> callable = Executors.callable(runnableTask);
    
    // 将Runnable+结果包装为Callable
    Callable<String> callable = Executors.callable(runnableTask, "预设返回值");
    

结合 CompletableFuture 的现代用法

// 使用Callable获取结果
CompletableFuture.supplyAsync(() -> {
    try {
        return callableTask.call();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
});

// 使用Runnable忽略结果
CompletableFuture.runAsync(runnableTask);

高频面试题扩展

  1. 为什么要有两种接口?
    Java早期版本(1.0)的线程模型需要简单任务接口,而 Java 5 引入并发库时需要支持更丰富的任务特性。

  2. 如何统一处理两种任务?

    ExecutorService executor = ...;
    Future<?> future1 = executor.submit(runnableTask);
    Future<Integer> future2 = executor.submit(callableTask);
    
  3. 性能差异?
    两者本身无显著性能差异,但 CallableFuture 处理会带来轻微开销。

posted @ 2025-03-06 22:32  皮皮是个不挑食的好孩子  阅读(266)  评论(0)    收藏  举报