Executor接口通过分离“做什么”和“谁来做、怎么跑”实现解耦,仅定义execute(Runnable)方法,使业务代码不依赖具体线程管理策略;误用new Thread().start()会导致线程泛滥、OOM等问题。

Executor接口如何解耦任务与执行逻辑
核心是把「做什么」和「谁来做、怎么跑」分开。Executor 只定义一个 execute(Runnable) 方法,调用方只管提交任务,完全不关心线程创建、复用或销毁。这种分离让上层代码不绑定具体线程管理策略,比如换成 ForkJoinPool 或自定义调度器,只要实现 Executor 接口,业务代码一行都不用改。
常见误用是直接 new Thread().start(),结果线程泛滥、GC 压力大、OOM 频发——这恰恰违背了 Executor 的初衷。
ThreadPoolExecutor 的关键参数怎么影响行为
真正干活的是 ThreadPoolExecutor,它靠五个参数决定线程池的脾气:
-
corePoolSize:常驻线程数,即使空闲也不回收(除非设置allowCoreThreadTimeOut(true)) -
maximumPoolSize:线程数上限,只有当任务队列满且当前线程 -
keepAliveTime:非核心线程空闲超时后被回收,单位由unit指定 -
workQueue:任务缓冲区,选错会直接导致拒绝或内存暴涨(比如用LinkedBlockingQueue无界队列,OOM 风险极高) -
handler:拒绝策略,AbortPolicy抛RejectedExecutionException,CallerRunsPolicy让提交线程自己执行任务(可缓解压力但可能拖慢调用方)
为什么 Executors 工具类创建的线程池在生产环境要慎用
Executors.newFixedThreadPool()、newCachedThreadPool() 看似方便,但隐藏严重隐患:
立即学习“Java免费学习笔记(深入)”;
-
newFixedThreadPool(n)内部用的是无界LinkedBlockingQueue,任务积压时内存持续上涨,最终 OOM -
newCachedThreadPool()允许线程数无限增长(上限Integer.MAX_VALUE),突发流量下可能瞬间创建几千线程,打爆系统资源 -
newSingleThreadExecutor()和newScheduledThreadPool()同样使用无界队列,不具备背压能力
正确做法是显式构造 ThreadPoolExecutor,明确指定有界队列(如 ArrayBlockingQueue(1000))和合适的拒绝策略。
submit() 和 execute() 的区别不只是返回值
execute() 是 Executor 接口方法,只接受 Runnable,不返回结果;submit() 是 ExecutorService 扩展方法,支持 Runnable 和 Callable,返回 Future。
关键差异在于异常处理:
- 用
execute()提交的Runnable,若 run() 抛异常,会直接交给Thread.UncaughtExceptionHandler,主线程无法感知 - 用
submit()提交的Callable,异常会被包装进Future.get()抛出,调用方能捕获并处理 - 即使
submit(Runnable),也得靠Future.get()才能发现执行期异常(否则静默丢失)
没做 get() 或没处理 ExecutionException,等于把异常扔进黑洞——这是线上问题排查最常忽略的一环。









