线程池执行1个任务后抛出RejectedExecutionException如何解决
Java并发ThreadPoolExecutor任务拒绝异常修复
异常信息
Task java.util.concurrent.FutureTask@341cf993 rejected from java.util.concurrent.ThreadPoolExecutor@122db90f [Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1]
问题代码
private ExecutorService executorService = Executors.newFixedThreadPool(300); ... collection.forEach(x -> { executorService.submit(new SenderThread(sender, params));}); executorService.shutdown();
场景说明:待处理的集合元素仅10-20个,远小于线程池配置的300线程数,执行时仅1个任务完成就抛出RejectedExecutionException,剩余任务全部未执行。
根因分析
异常直接指向核心问题:提交任务时线程池已经处于Terminated终止状态,不再接收新任务。问题本质是线程池生命周期管理逻辑错误:
- 你将线程池定义为类级别的共享成员变量,却在业务方法中直接调用
shutdown()关闭线程池。 - 触发场景分为两类:
- 方法被重复调用:第一次方法执行完成后线程池被永久关闭,后续调用再向已终止的线程池提交任务就会触发拒绝。
- 多线程并发调用方法:一个线程还在遍历集合提交任务,另一个线程已经执行到
shutdown()触发生效,线程池开始拒绝新任务,后续未提交的任务全部失败;异常抛出时之前提交的1个任务刚好执行完成,就会出现日志里completed tasks = 1的状态。
额外问题:不建议把Thread子类作为任务提交给线程池,线程池本身负责工作线程的创建和调度,只需要传入实现Runnable/Callable的任务逻辑即可,传入Thread实例属于无意义的资源开销。
修复方案
- 单次批量任务场景:将线程池改为方法局部变量,避免共享导致的生命周期混乱,同时正确等待所有任务执行完成再关闭池。参考代码:
// 方法内创建局部线程池,线程数和实际任务量匹配即可 ExecutorService executorService = Executors.newFixedThreadPool(Math.min(collection.size(), 300)); try { // 提交Runnable实现类,不要传Thread对象 collection.forEach(x -> executorService.submit(new SenderRunnable(sender, params))); } finally { executorService.shutdown(); // 等待所有任务执行完成,设置合理超时避免永久阻塞 try { if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } }
- 全局通用线程池场景(比如Spring容器托管的线程池Bean):禁止在业务代码中手动调用shutdown()方法,全局线程池的生命周期由容器统一管理,手动关闭会导致所有后续请求提交任务被拒。
- 多线程共享线程池场景:如果确实需要多线程共享同一个线程池并手动关闭,必须加同步控制,保证所有任务全部提交完成之后,再执行shutdown操作,避免提交过程中池被提前关闭。
内容的提问来源于stack exchange,提问作者Aras
相关产品推荐
相关产品推荐

