线程池默认不保证任务执行顺序;严格FIFO需单线程池+无界/足够容量阻塞队列;newSingleThreadExecutor最简单;手动构造时避免ArrayBlockingQueue容量过小和拒绝策略误用;分组有序可用ConcurrentHashMap+单线程池。

线程池默认不保证任务执行顺序
Java 的 ThreadPoolExecutor 本身不维护任务提交顺序与执行顺序的一致性——哪怕你用 newFixedThreadPool(1),只要任务在运行中被中断、拒绝或线程被复用,就可能出现看似“乱序”的现象。真正能约束顺序的,是任务提交方式 + 队列类型 + 线程数三者共同作用的结果。
关键判断点:如果你需要严格 FIFO(先提交先执行),必须满足两个条件:单线程池 + 无界/有界但不触发拒绝策略的阻塞队列;否则,多线程下天然存在竞争和调度不确定性。
用 newSingleThreadExecutor() 最简单实现顺序执行
这是最直接、副作用最少的方式。它内部封装了一个 FinalizableDelegatedExecutorService 包裹的单线程池,底层队列是 LinkedBlockingQueue(无界),天然 FIFO。
- 适合场景:日志写入、状态机流转、串行化外部 API 调用等对时序敏感且吞吐压力不大的任务
- 注意点:
shutdownNow()可能中断正在执行的任务,若需优雅终止,应配合awaitTermination()和任务自身可中断逻辑 - 不能用于 CPU 密集型长任务,否则会阻塞后续所有任务
自定义单线程池时别踩 ArrayBlockingQueue 容量陷阱
如果不用 newSingleThreadExecutor(),而是手动构造 ThreadPoolExecutor,常见错误是用小容量 ArrayBlockingQueue 却忽略拒绝策略:
立即学习“Java免费学习笔记(深入)”;
new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue(1), // ⚠️ 容量为 1,第 2 个任务就会触发拒绝
new ThreadPoolExecutor.AbortPolicy()
);
此时第 2 个任务提交会直接抛出 RejectedExecutionException,而不是排队等待。正确做法是:
- 用
LinkedBlockingQueue(无界)或设足够大容量的ArrayBlockingQueue - 避免使用
AbortPolicy或DiscardPolicy,改用CallerRunsPolicy(让提交线程自己执行)或自定义策略 - 确认核心线程数和最大线程数都为 1,否则多线程会破坏顺序
想部分有序?用 PriorityBlockingQueue 或分组+单线程池
纯 FIFO 不够用时(比如按优先级、按 key 分组串行),PriorityBlockingQueue 是一个选择,但它不保证同优先级任务的插入顺序,且需实现 Comparable 或传入 Comparator。更稳妥的做法是逻辑分组:
- 对任务打标(如
userId),用ConcurrentHashMap维护每个 key 对应的单线程池 - 提交时路由到对应池:
executors.get(userId).submit(task) - 注意清理空闲池,避免内存泄漏(可用
ScheduledExecutorService定期扫描 +isTerminated()判断)
这种模式在消息消费、用户行为聚合等场景很常见,但要注意线程池数量膨胀问题——几十万用户不能起几十万个单线程池。










