前言
在 Java 并发编程中,线程池是核心技术之一,而 execute() 和 submit() 是线程池最常用的两个方法。很多开发者只停留在表面认识——execute 抛异常,submit 返回 Future,但这种理解远远不够。
本文将从源码层面深度解析这两个方法的本质差异,并通过实战案例演示它们的适用场景。
一、核心差异一览
| 维度 | execute() | submit() |
|---|---|---|
| 返回值 | void | Future |
| 异常传播 | 任务内异常会直接抛出到 UncaughtExceptionHandler,主线程无法感知 | 异常被 FutureTask 捕获并存储,调用 get() 时才抛出 ExecutionException |
| 任务类型 | 仅支持 Runnable | 支持 Runnable 和 Callable |
| 适用场景 | 不关心结果的异步任务(如日志发送、数据清理) | 需要获取结果或处理异常的任务(如计算、RPC 调用) |
| 接口定义 | Executor 接口 | ExecutorService 接口 |
二、源码层面解析
2.1 submit() 的源码实现
// AbstractExecutorService.java
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 关键点 1:将 Runnable 包装为 RunnableFuture
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
// 关键点 2:最终还是调用 execute()
return ftask;
// 关键点 3:返回 Future 对象
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<>(runnable, value);
}
核心洞察:submit() 本质上是 execute() 的包装器,它在调用 execute() 前做了两件事:
- 任务包装:将 Runnable/Callable 包装成 FutureTask

