executors.newsinglethreadexecutor() 能保证任务按提交顺序执行,因其采用单线程+无界fifo队列,异常终止后自动重建线程续执,而newfixedthreadpool(1)则不会重建。

Executors.newSingleThreadExecutor() 确实能保证任务按提交顺序执行——前提是不主动中断线程、不抛出未捕获的 Throwable、且任务本身不自行 fork 新线程破坏串行性。
为什么它能保序:单线程 + 无界队列
这个执行器底层是 FinalizableDelegatedExecutorService 包装一个 ThreadPoolExecutor,核心参数为:corePoolSize=1、maximumPoolSize=1、workQueue=new LinkedBlockingQueue()(无界)。所有任务进队列,唯一工作线程从队首逐个取任务执行。
- 队列是
LinkedBlockingQueue,FIFO 语义严格,不会重排序 - 没有线程竞争,不存在多线程下指令重排或可见性干扰
- 如果某个任务抛出未捕获异常,该线程会终止,但
newSingleThreadExecutor会自动创建新线程接续队列,**仍保持顺序**(这是它和newFixedThreadPool(1)的关键区别)
常见破序场景与规避方式
保序不是“绝对可靠”,而是依赖正确使用。以下操作会直接破坏顺序保障:
- 在任务中调用
System.exit()或导致 JVM 崩溃——线程池无法恢复,队列任务丢失 - 任务内启动新线程(如
new Thread(...).start())并异步修改共享状态——逻辑上已脱离串行上下文 - 使用
Future.cancel(true)强制中断正在运行的任务——可能打断执行流,后续任务虽仍按序入队,但前序任务的副作用可能不完整 - 向执行器提交
Runnable时传入 null——会立即抛NullPointerException,但该异常发生在提交线程,不影响队列和已有任务
对比 newFixedThreadPool(1):一个容易忽略的差异
很多人误以为两者等价,但它们对线程生命周期的处理不同:
-
newSingleThreadExecutor():内部线程异常终止后,会新建线程继续消费队列;shutdown 后不可重启 -
newFixedThreadPool(1):线程异常终止后,**不会重建**,队列积压任务将永久挂起,直到手动干预 - 因此,若任务有潜在崩溃风险(如调用不稳定的外部 SDK),优先选
newSingleThreadExecutor()
验证方式很简单:
ExecutorService es = Executors.newSingleThreadExecutor();
es.submit(() -> { throw new RuntimeException("boom"); });
es.submit(() -> System.out.println("still runs")); // 这行会被打印
需要额外注意的边界点
顺序只针对「成功提交到执行器」的任务。以下情况不被保证:
- 多个线程并发调用
submit():JVM 不保证这些 submit 调用本身的执行时序(虽然实际中几乎总是按调用顺序入队,但这是LinkedBlockingQueue.offer()的实现细节,非 JMM 保证) - 任务内部使用
System.nanoTime()或Thread.sleep()做时间判断——时间片调度、GC 暂停会导致实际执行间隔不可控 - 关闭执行器后调用
submit():抛RejectedExecutionException,任务根本没进队列
真正严格的顺序依赖,建议在任务提交前由调用方加锁或用 AtomicInteger 手动编号,而不是仅靠执行器行为。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











