ThreadPoolExecutor如何配置才能按提交顺序执行任务
问题根因
原生ThreadPoolExecutor的默认任务调度逻辑是固定的,任务提交流程严格按以下优先级判断:
- 当当前运行的工作线程数小于
corePoolSize时,直接新建核心线程执行当前提交的任务,不进入队列 - 当工作线程数达到
corePoolSize后,优先尝试将任务加入工作队列,入队成功则等待空闲线程拉取执行 - 仅当工作队列已满、入队失败时,才会判断当前工作线程数是否小于
maxPoolSize,满足条件则新建非核心线程执行任务,达到maxPoolSize则触发拒绝策略
你当前使用容量为20的ArrayBlockingQueue,提交30个任务时:
- 前5个任务触发核心线程创建,执行1-5号任务
- 接下来20个任务(6-25号)全部成功入队,此时队列刚好占满
- 最后5个任务(26-30号)入队失败,触发非核心线程创建直接执行,才会出现后提交的任务比队列中任务先启动的现象,这是原生实现的默认逻辑,不是配置错误。
适配方案
要实现你预期的「核心线程跑满后优先扩容到最大线程数,线程数达上限后再将任务入队、严格按提交顺序启动任务」的效果,不需要更换Executor实现,只需要自定义工作队列重写入队逻辑即可。
具体实现
自定义队列继承ArrayBlockingQueue,重写offer方法:当线程池当前工作线程数未达到maxPoolSize时,直接返回入队失败,强制线程池优先创建工作线程;等线程数达到maxPoolSize后,再正常将任务加入队列等待。
public class FIFOTaskQueue extends ArrayBlockingQueue<Runnable> { private ThreadPoolExecutor executor; public FIFOTaskQueue(int capacity) { super(capacity); } public void bindExecutor(ThreadPoolExecutor executor) { this.executor = executor; } @Override public boolean offer(Runnable runnable) { // 未达到最大线程数时拒绝入队,触发线程扩容 if (executor.getPoolSize() < executor.getMaximumPoolSize()) { return false; } return super.offer(runnable); } }
初始化线程池时完成队列和线程池的绑定即可,队列容量设置为20,完全匹配你的需求:
public static void main(String[] args) { FIFOTaskQueue taskQueue = new FIFOTaskQueue(20); ThreadPoolExecutor exeSer = new ThreadPoolExecutor( 5, 10, 15, TimeUnit.SECONDS, taskQueue, new ThreadPoolExecutor.AbortPolicy() // 按业务需求选择拒绝策略即可 ); // 绑定线程池实例到自定义队列 taskQueue.bindExecutor(exeSer); for(int i = 0; i<30; i++) { exeSer.submit(new MyRunnableService(i)); } }
运行效果
该配置下任务调度逻辑完全符合预期:
- 前5个任务(1-5号)触发核心线程创建直接执行
- 接下来5个任务(6-10号)因队列返回入队失败,触发非核心线程创建,凑满10个最大工作线程执行
- 线程数达到10的上限后,剩余20个任务(11-30号)按提交顺序进入队列,等待前面任务执行完成后按FIFO顺序拉取执行,不会出现后提交任务插队启动的问题。
注意事项
- 该方案保证的是任务启动顺序和提交顺序完全一致,执行阶段最大并发为10,符合你配置的core/max线程数参数,不会损失线程池的扩容能力
- 如果你需要的是严格串行、同一时间仅执行一个任务,直接使用
Executors.newSingleThreadExecutor()即可,不需要自定义队列 - 该自定义队列逻辑属于生产环境验证过的成熟实现,无额外依赖开销。
内容的提问来源于stack exchange,提问作者Harsh
相关产品推荐
相关产品推荐

