在 Java 并发编程的世界里,多线程是我们必须掌握的核心技能。当你开始编写多线程代码时,最先遇到的概念可能就是 INLINECODEafe65ffa 接口。不过,随着业务逻辑变得复杂,你很快会发现 INLINECODEd11eafab 有一个明显的局限性:它无法返回执行结果,也不能抛出受检异常。这正是 Callable 接口大显身手的地方。
在这篇文章中,我们将深入探讨 Java 中这两个至关重要的接口——INLINECODEb7793000 和 INLINECODEb3032482。我们不仅要了解它们的基本用法,更要通过实战代码和底层原理,彻底搞懂它们之间的区别,以及在什么场景下应该选择哪一个。
为什么我们需要关注这两个接口?
在 Java 中,线程是宝贵的资源,而线程的任务逻辑通常需要被封装起来以便于线程执行。INLINECODE7419bcd2 和 INLINECODE130d30ea 就是 Java 提供给我们的两种任务封装方式。虽然它们的目的相似——都是为了在多线程环境中执行代码——但它们在设计上有着本质的不同。理解这些差异,能帮助我们写出更健壮、更高效的多线程应用。
初识 Runnable 接口:老当益壮的基础
INLINECODEb787ea18 是 Java 早期版本(JDK 1.0)就存在的接口,它位于 INLINECODEfc4667e4 包中。它的设计非常简单,旨在将一段代码逻辑与“执行这段代码的线程”分离开来。
Runnable 的定义
让我们先看看它的源码定义。这是一个函数式接口,只包含一个无参数、无返回值的 run() 方法:
public interface Runnable {
public abstract void run();
}
核心特点
- 无返回值:这是 INLINECODE14a3b88b 最显著的特征。INLINECODEf1f4333f 方法被设计为
void,意味着如果你想在任务执行完后获取计算结果,必须通过共享变量或者回调函数来间接实现,这往往会导致代码耦合度变高。 - 异常处理受限:INLINECODE0eecba8d 方法不允许抛出受检异常。这意味着你在任务逻辑中必须自己 INLINECODE64c841d9 处理所有可能的受检异常,否则编译器会报错。这给异常的集中处理带来了麻烦。
- 执行方式灵活:虽然它主要与 INLINECODE34aa9a4a 类配合使用,但它也是现代线程池 INLINECODEddfd3c14 的核心参数类型之一。
实战示例 1:使用 Runnable 启动线程
让我们看一个最基础的例子,展示如何通过 Runnable 定义任务并启动线程。
// 定义一个实现了 Runnable 接口的任务类
class SimpleTask implements Runnable {
@Override
public void run() {
// 模拟耗时操作
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行任务...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 注意:这里我们必须手动处理中断异常
Thread.currentThread().interrupt(); // 恢复中断状态
System.err.println("任务被中断");
}
System.out.println("任务执行完毕。");
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 方法1:直接通过 Thread 类启动
SimpleTask task = new SimpleTask();
Thread thread = new Thread(task);
thread.start(); // JVM 会调用 task.run()
// 方法2:使用 ExecutorService 线程池启动(推荐)
// Executors 是线程池工厂类
var executor = Executors.newFixedThreadPool(1);
executor.submit(task); // 提交任务
// 关闭线程池(防止资源泄漏)
executor.shutdown();
}
}
代码解析:在这个例子中,我们创建了 INLINECODE04835c6c 类来定义具体的业务逻辑。你可以看到,我们既可以使用传统的 INLINECODE91a99983 类,也可以使用更现代的 INLINECODE0233321c 来运行它。注意 INLINECODEd3cb9403 方法里的 try-catch 块,这正是 Runnable 处理异常的局限性所在。
进阶 Callable 接口:为并发而生
到了 JDK 1.5,Java 引入了强大的并发包 INLINECODEd0ac34d9(简称 JUC)。在这个包中,INLINECODE876d6cb3 接口应运而生。它的出现弥补了 Runnable 无法返回结果和抛出受检异常的缺陷。
Callable 的定义
Callable 是一个泛型接口,看起来是这样的:
public interface Callable {
V call() throws Exception;
}
这里的 `INLINECODEd7940f89CallableINLINECODE6bb126e8call()INLINECODE929c09b6call()INLINECODE7ab85c83ExceptionINLINECODE6284708aRunnableINLINECODE10a77bb7Thread(new Callable())INLINECODE13ad68c1CallableINLINECODE004052ceExecutorServiceINLINECODE33ab7a67CallableINLINECODE4ae01d0aFutureINLINECODEf3a80348mainINLINECODE2c131c8efuture.get()INLINECODE52a5a438mainINLINECODE1a35a1bacatchINLINECODEf6b045caCallableINLINECODE49d19f0bcall()INLINECODEfb9bb3e8ExecutionExceptionINLINECODEcfa9a873public void run()INLINECODE0ceed4ccV call() throws ExceptionINLINECODE1f97dcafThreadINLINECODE77cdd5feExecutorServiceINLINECODE60d1339dExecutorServiceINLINECODE9278e88cThreadINLINECODEd0c52c67RunnableINLINECODEdde3583bCallableINLINECODEc23f214eRunnableINLINECODEc445350bCallableINLINECODEbbbae45bFutureINLINECODE289cac8bCallableINLINECODEda4f010dRunnableINLINECODEae7fcf82longINLINECODE97a42928synchronizedINLINECODE3693ea4fCallableINLINECODE383c2d1dfuture.get()INLINECODE6849af3dget()INLINECODE47123a95CallableINLINECODEd214c08dExecutorService.invokeAllINLINECODE786d9f73submitINLINECODE992ef7afgetINLINECODE43474617implements RunnableINLINECODE8094f8e5CallableINLINECODE64ed27d3CallableINLINECODE99e14f56RunnableINLINECODEb4bd9601CallableINLINECODE7d518213RunnableINLINECODEbe63543eCallableINLINECODE2dd28e5dExecutorServiceINLINECODEa6e4013bCallableINLINECODE52896c5cFutureINLINECODE39048de9Callable 和 Future` 模式。这将是你向高级 Java 工程师迈进的重要一步。