Java线程池中execute()和submit()的区别全解析(源码&实战)
作者:Knight_AL
这篇文章主要介绍了Java线程池中execute()和submit()区别的相关资料,它们之间的主要区别在于submit方法可以返回结果、捕获异常、支持任务取消和批量汇总结果,而execute方法则不关心返回值,异常会直接抛出,需要的朋友可以参考下
前言
在 Java 并发编程里, ThreadPoolExecutor 是最常用的组件之一,而其中最常用的两个方法就是:
execute(Runnable command)
submit(Callable/Runnable task)
看起来只有“是否有返回值”的差别?
其实远不止如此!
一、简单对比:一张表记住核心差异
| 项目 | execute() | submit() |
|---|---|---|
| 所属接口 | Executor | ExecutorService |
| 接受参数 | Runnable | Runnable / Callable |
| 是否有返回值 | ❌ 无 | ✔ Future,可取消,可拿结果 |
| 异常处理 | ❗异常直接抛出到线程的 UncaughtExceptionHandler | 🔒 异常被捕获放进 Future(不会直接打印) |
| 是否可取消 | ❌ 无 | ✔ 可用 Future.cancel() |
| 是否支持批量汇总结果 | ❌ 不支持 | ✔ 支持 Future + CompletionService |
一句话总结:
execute 用来“执行任务”, submit 用来“管理任务”。
二、execute():轻量级任务执行,不关心结果
executor.execute(() -> {
System.out.println("logging...");
int x = 1 / 0; // 会打印异常栈
});

特点:
- 不关心返回值
- 异常会直接抛到控制台
- 性能开销更小(不创建 FutureTask)
适用场景:
- 记录访问日志
- 异步发 MQ、埋点
- 刷缓存、通知等“扔过去就不管了”的任务
三、submit():带状态的任务管理,有返回值,可捕获异常
Future<Integer> future = executor.submit(() -> {
return 123;
});
System.out.println(future.get()); // 结果 123

submit 最大的隐形区别:异常不会自动抛出
Future<?> f = executor.submit(() -> {
throw new RuntimeException("error");
});
f.get(); // ExecutionException:异常从这里抛出

为什么?
因为 submit 内部把任务包成了 FutureTask,它会:
try {
call()
} catch (Throwable t) {
setException(t); // 保存到 Future
}
→ 所以异常不会直接爆出来,而是等你 future.get() 再抛。
四、源码视角:submit 底层其实也是调用 execute
submit 并不是独立执行任务,它是:
- 把 Runnable/Callable 包装成 FutureTask
- 调用 execute 执行 FutureTask
源码(摘自 AbstractExecutorService):
public <T> Future<T> submit(Callable<T> task) {
RunnableFuture<T> f = newTaskFor(task);
execute(f); // 注意这里!
return f;
}
而 execute 是线程池真正调度任务的入口。
五、异常处理的根本差别(重点!)
| 方法 | 任务内部抛异常会怎样? |
|---|---|
| execute | 异常会冒泡到线程顶层 → 默认打印栈追踪 |
| submit | 异常被 FutureTask 捕获 → 不会打印,需要 future.get() 才能感知到 |
示例:submit 吃掉异常
executor.submit(() -> {
throw new RuntimeException("Boom!");
});
// 控制台不会输出任何异常
如果你不 get(),异常就彻底静悄悄消失了,这在真实项目里非常危险。
“为什么 submit() 会吃掉异常?怎么处理?”
答案:用 get() 捕获 ExecutionException,从 cause 拿真正异常。
六、真实业务场景如何选择?
✔ execute() 适用于:
- 不需要返回值
- 不需要任务取消
- 不关心任务是否失败
典型业务:
- 访问日志
- 埋点
- 异步通知
- 缓存刷新
✔ submit() 适用于:
- 需要汇总结果(并发调用多个服务)
- 需要捕获异常
- 需要任务状态(是否完成、是否失败)
- 需要 cancel
典型业务(例如医疗系统):
- 药审 + 过敏 + 医保试算:并发执行,返回综合结果
- 大报表导出:后台执行 + 查询状态 + 可取消
- 风险评估:多线程计算指标后统一合并
- 第三方接口调用:需要捕获异常、重试、降级
七、一个真实示例:医疗系统并发校验医嘱
Future<DrugAuditResult> drugF = pool.submit(() -> drugAudit());
Future<AllergyResult> allergyF = pool.submit(() -> checkAllergy());
Future<InsuranceResult> insF = pool.submit(() -> preCalcInsurance());
try {
DrugAuditResult d = drugF.get();
AllergyResult a = allergyF.get();
InsuranceResult i = insF.get();
return new Summary(d, a, i);
} catch (ExecutionException e) {
log.error("校验失败", e.getCause());
throw new BusinessException("医嘱校验失败");
}
这里必须用 submit,因为你要:
- 拿返回值
- 捕获异常
- 汇总结果
八、最终总结
execute 和 submit 的本质区别:
| 关键点 | execute | submit |
|---|---|---|
| 是否包装 Future? | ❌ 否 | ✔ 是 |
| 异常如何处理? | 直接抛出 | 存 Future 内,get 时抛 |
| 是否有返回值? | 无 | 有 |
| 是否可取消? | 否 | 是 |
| 是否适合任务编排/汇总结果? | ❌ | ✔ |
execute 用来“执行任务”, submit 用来“管理任务”。
到此这篇关于Java线程池中execute()和submit()区别的文章就介绍到这了,更多相关Java线程池execute()和submit()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
