bigywater.tech
Java · 144

线程池:execute() 如何决定进队列还是创建线程

ThreadPoolExecutor 的 execute() 到底怎么决定进队列还是创建线程?不同队列会怎样?任务抛异常线程会怎样?这篇用真实的线程池把每一步量出来(拒绝策略的四种表现已有专门的文章)。

核心 → 队列 → 非核心 → 拒绝
execute 的决策顺序
线程 2,队列 28
无界队列提交 30 个任务时 max=8 根本没用上
worker-1…4
execute 抛 3 次异常后线程工厂创建过的线程

决策顺序

实测 · 本机执行输出
  core=2, max=4, 队列容量=2。依次提交 7 个会阻塞的任务:
  任务 1:已接受 → 线程数 0 → 1,队列里 0
  任务 2:已接受 → 线程数 1 → 2,队列里 0
  任务 3:已接受 → 线程数 2 → 2,队列里 1
  任务 4:已接受 → 线程数 2 → 2,队列里 2
  任务 5:已接受 → 线程数 2 → 3,队列里 2
  任务 6:已接受 → 线程数 3 → 4,队列里 2
  任务 7:被拒绝(RejectedExecutionException) → 线程数 4 → 4,队列里 2

队列类型

实测 · 本机执行输出
  LinkedBlockingQueue(无界)    core=2 max=8,提交 30 个阻塞任务:线程数 2,队列 28,被拒绝 0
  ArrayBlockingQueue(20)     core=2 max=8,提交 30 个阻塞任务:线程数 8,队列 20,被拒绝 2
  SynchronousQueue           core=2 max=8,提交 30 个阻塞任务:线程数 8,队列 0,被拒绝 22

线程的回收

实测 · 本机执行输出
  同时有 6 个任务在跑:线程数 6(最大 6)
  任务结束后 ~0 ms:线程数 6
  任务结束后 ~200 ms:线程数 6
  任务结束后 ~500 ms:线程数 2
  任务结束后 ~1000 ms:线程数 2
  allowCoreThreadTimeOut(true) 之后再等 500 ms:线程数 0

任务抛异常

实测 · 本机执行输出
  execute 的任务在 worker-1 里运行并抛异常
  execute 的任务在 worker-2 里运行并抛异常
  execute 的任务在 worker-3 里运行并抛异常
  [未捕获异常处理器] worker-3 收到 java.lang.IllegalStateException: boom
  [未捕获异常处理器] worker-1 收到 java.lang.IllegalStateException: boom
  [未捕获异常处理器] worker-2 收到 java.lang.IllegalStateException: boom
  execute 抛了 3 次异常之后,线程工厂一共创建过 [worker-1, worker-2, worker-3, worker-4](每次异常都换了新线程)
  submit 抛了 3 次异常之后,线程工厂创建过 [worker-s1];未捕获异常处理器没有被调用
  只有调用 future.get() 才看到:java.lang.IllegalStateException: boom

分步演示

线程池决策
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:ForkJoinPool 与 ScheduledThreadPoolExecutor 的差异、线程池关闭(shutdown/shutdownNow)的细节、CallerRunsPolicy 等拒绝策略(已有文章)、JDK 17 与 27 的结果是否相同;keepAlive 回收的精确时刻是一次运行的观察
    京ICP备2026067502号-1京公网安备11010502063487号