submit() 包装异常至Future.get(),execute() 直接抛出;submit返回Future可同步等待或取结果,execute无反馈;忽略submit的Future会导致异常丢失、内存泄漏和无法取消。

submit() 会包装异常,execute() 直接抛出
这是最常踩的坑:用 execute() 提交 Runnable 时,如果任务内部抛出未捕获异常,线程池默认会打印堆栈并终止该工作线程(但池本身继续运行);而 submit() 把任务包装成 FutureTask,异常被吞进 Future.get() 里,不调用 get() 就永远看不到。
- 场景:后台定时任务、异步日志写入 —— 若用
execute(),异常沉默失败;若用submit()却忘了future.get()或future.isDone()检查,等于埋雷 - 注意:
submit(Runnable)返回的Future调用get()只会返回null,异常仍需捕获;submit(Callable)才能拿到真实返回值和异常 - 调试技巧:在
ThreadFactory中设置未捕获异常处理器,或给线程池配置new ThreadPoolExecutor.CallerRunsPolicy()配合日志,才能兜住execute()的异常漏网
返回值类型决定你能不能取结果或等完成
execute() 是 void 方法,纯“发出去就不管”;submit() 必然返回 Future,哪怕你传的是 Runnable —— 因为它内部强制转成了 FutureTask<Void>。
-
submit(Runnable task)→Future<?>,get()返回null,但可用来同步等待执行结束(future.get()阻塞到任务完) -
submit(Callable<T> task)→Future<T>,get()返回计算结果,且异常必须显式处理 - 别误以为
execute()更“轻量”——底层调度开销几乎一样,差异只在封装层;选哪个取决于你是否需要反馈,不是性能
拒绝策略对 execute() 和 submit() 的影响不同
当线程池饱和(队列满 + 线程数达上限),拒绝策略触发时机一致,但表现不同:因为 submit() 的任务实际是 FutureTask 实例,而 execute() 是裸 Runnable。
- 使用
AbortPolicy(默认):两者都会抛RejectedExecutionException,但submit()抛在提交时,execute()也抛在提交时 —— 表面一样,实则submit()多一次对象包装,异常堆栈略深一层 - 使用
CallerRunsPolicy:两者都退回到调用线程执行,但submit()的任务执行后,其Future状态变为完成,get()不再阻塞;而execute()没这层状态管理 - 自定义拒绝策略时,如果依赖任务类型做判断(比如区分 Runnable/Callable),务必注意
submit(Runnable)传进去的其实是FutureTask,不是原始 Runnable
不要用 submit() 包裹无返回任务还丢弃 Future
常见反模式:executor.submit(() -> doSomething()); 却不保存返回的 Future,也不调用 get() 或 cancel()。这会导致:
立即学习“Java免费学习笔记(深入)”;
- 异常永远丢失:即使任务抛了
NullPointerException,也没人 catch - 内存泄漏风险:某些线程池实现中,未完成的
FutureTask可能长期持引用(尤其配合 ScheduledThreadPoolExecutor 时) - 无法取消:任务已启动但卡住,你手头没
Future就没法cancel(true) - 正确做法:要么用
execute()(接受沉默失败),要么存下Future并至少做一次isDone()或超时get(1, TimeUnit.SECONDS)
Future.get();如果不确定,宁可用 execute() 配合全局异常处理器,也别假装提交了 submit() 就算处理了错误。








