一、先看最直观的区别
- execute() :提交任务,无返回值,异常直接抛出,线程池自己处理。
- submit() :提交任务,返回 Future ,异常被 "吃掉",只有调用
get()才抛出。
二、方法定义与来源
1. execute () 来自哪里?
void execute(Runnable command);
来自顶层接口 Executor 只能提交 Runnable无返回值
2. submit () 来自哪里?
Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);
来自 ExecutorService 接口支持提交 Runnable + Callable 返回 Future 对象
三、核心区别 1:返回值(最常用)
1)execute () ------ 没有返回值
executor.execute(() -> {
System.out.println("任务执行");
});
执行完就完了,拿不到结果。
适合:
- 日志记录
- 异步通知
- 不需要结果的纯异步任务
2)submit () ------ 有返回值 Future
Future<Integer> future = executor.submit(() -> {
return 100;
});
// 获取结果
Integer res = future.get();
通过 future.get() 可以阻塞获取任务结果。
适合:
- 需要获取异步计算结果
- 需要知道任务是否执行成功
- 需要处理任务异常
四、核心区别 2:支持的任务类型
execute()
只支持 Runnable
executor.execute(Runnable);
submit()
支持 Runnable + Callable
executor.submit(Runnable);
executor.submit(Callable);
Callable 可以:
- 有返回值
- 抛出异常这是 execute 做不到的。
五、核心区别 3:异常处理(最重要!)
这是生产最容易踩坑 、面试最高频的点。
1)execute ():异常直接抛出
任务抛异常 → 直接抛出 → 线程终止 → 线程池会新建一个线程补上。
executor.execute(() -> {
// 异常直接抛出
int i = 1 / 0;
});
控制台直接看到异常。
2)submit ():异常被 "吃掉",不 get 不抛出
submit 不会直接抛出异常!异常会存到 Future 里 ,只有调用 future.get() 才会抛出。
Future<?> future = executor.submit(() -> {
int i = 1 / 0;
});
// 不调用 get(),你永远不知道这里抛了异常!
巨大坑点:
很多人用 submit 提交任务,不接收 Future、不调用 get () 结果任务报错了,日志没有、系统没反应、问题查不到。
六、核心区别 4:底层原
execute () 流程
- 提交任务
- 线程池分配线程执行
- 执行出错直接抛出
- 无返回值
Submit () 底层做了什么?
- 把任务包装成 FutureTask
- 执行任务
- 异常 / 结果都存入 FutureTask
- 返回 Future 对象
- 调用 get () 才返回结果或异常
| 对比项 | execute() | submit() |
|---|---|---|
| 来源接口 | Executor | ExecutorService |
| 参数类型 | 仅 Runnable | Runnable + Callable |
| 返回值 | 无 | Future |
| 获取结果 | 不支持 | 支持:future.get () |
| 异常处理 | 直接抛出 | 存入 Future,get () 才抛出 |
| 使用成本 | 低 | 稍高(需要处理 Future) |
| 生产推荐场景 | 异步、记录、发送消息 | 需要结果、需要异常捕获 |
七、生产环境到底怎么选?
1)优先用 execute ()
不需要返回值、不需要捕获异常例如:
- 记录日志
- 发送 MQ
- 刷新缓存
- 更新埋点
好处:简单、无开销、不吞异常、方便排查。
2)必须用 submit ()
需要获取异步执行结果例如:
- 多线程并行计算
- 异步查询汇总
- 需要知道任务是否成功
注意:submit 必须接收 Future,必须调用 get (),必须捕获异常