线程池与 Executor 框架

共 118 题
📑 题目列表 118 题
#
★★★

1. 线程池任务排队时间与执行时间如何分开观测,队列深度上涨时应先调整 corePoolSize 还是队列容量

线程池任务排队时间与执行时间如何分开观测?队列深度上涨时应先调整 corePoolSize 还是队列容量?

  • 排队时间/执行时间观测
  • 队列深度
  • 参数调整

分开观测:用 ThreadPoolExecutor 的扩展点(beforeExecute/afterExecute)记录任务开始/结束时间,或用任务包装器记录提交时间戳,计算"排队时间"(提交到开始)与"执行时间"(开始到结束)。也可用 JFR 的线程池事件或 Metrics 埋点。队列深度上涨时:先判断瓶颈是"线程不足"还是"消费太慢"。若活跃线程已满且排队时间增长,说明线程不够,先调大 corePoolSize/maxPoolSize;若队列深度涨但线程未满(任务被队列吸收),说明队列容量与线程数失配,再考虑队列容量。原则:先评估线程利用率,再调队列,避免混淆。

分开观测排队/执行时间才能定位瓶颈。先看线程是否吃满,再决定调线程还是队列。

#
★★★

2. 用 PriorityBlockingQueue 作为 workQueue 时,高优先级任务在池饱和阶段为何仍可能被延迟,如何评估公平性代价

用 PriorityBlockingQueue 作为 workQueue 时,高优先级任务在池饱和阶段为何仍可能被延迟?如何评估公平性代价?

  • PriorityBlockingQueue
  • 优先级
  • 公平性代价

用 PriorityBlockingQueue 时,高优先级任务在队列中优先,但池饱和阶段(线程全忙、队列满)时:1) 队列满则新任务走拒绝策略,高优先级任务可能被拒绝;2) 即使队列不满,高优先级也需等当前运行任务完成(无法抢占运行中的线程);3) 优先级提升可能使低优先级任务饿死(长期不执行)。公平性代价:优先级队列破坏 FIFO 公平,低优先级被推迟,且重排序有 O(log n) 开销。评估:监控各优先级任务的平均等待时间与吞吐,判断优先级调度是否值得。

优先级队列只保证"队列内排队顺序",不保证抢占或拒绝时不丢。高优先级在饱和时仍可能被延迟/拒绝。

#
★★★

3. ForkJoinPool.commonPool 与业务隔离线程池在并行流下的资源竞争与饥饿问题如何排查

ForkJoinPool.commonPool 与业务隔离线程池在并行流下的资源竞争与饥饿问题如何排查?

  • commonPool
  • 并行流
  • 资源竞争/饥饿

并行流默认用 commonPool(并行度=核数-1)。若并行流中嵌套阻塞任务,commonPool 线程被阻塞占满,导致其他并行流任务饥饿(无线程可用)。排查:1) 观察 commonPool 活跃线程/队列(可通过 getCommonPoolParallelism 与线程 dump);2) 检查并行流任务是否含阻塞(IO/锁),阻塞会占满 commonPool;3) 用业务隔离线程池(自定义 ForkJoinPool)替代 commonPool,避免与其他并行流竞争;4) 用 ManagedBlocker 处理阻塞。定位:线程 dump 看 commonPool 线程是否都阻塞在任务上。

commonPool 被阻塞任务占满会导致并行流饥饿。隔离线程池 + 避免池内阻塞是排查关键。

#
★★★

4. ForkJoinTask 的 fork()/join() 在并行递归场景下的栈管理

ForkJoinTask 的 fork()/join() 在并行递归场景下的栈管理是什么?

  • fork/join
  • 栈管理
  • 递归

ForkJoinTask 的 fork() 把子任务放入工作队列(异步),join() 等待子任务完成。并行递归时,fork 子任务用工作窃取(work-stealing)调度,避免传统递归的深层栈。但 fork/join 的栈管理要点:1) 若任务无限递归,栈仍可能溢出(ForkJoinTask 用栈帧,但递归深度受任务拆分影响);2) 正确模式是"任务内递归调用 fork/join",用 compute 递归拆分;3) fork 后任务会排队,太多 fork 增大队列与内存;4) fork 适合"先 fork 再 join"的并行拆分,若立即 join 用 compute 直接调用更高效。栈管理靠任务拆分阈值与工作窃取。

fork/join 用工作窃取调度子任务,递归拆分需控制阈值,避免无限递归与过多 fork。

#
★★★

5. ForkJoinTask.join() 在线程池饱和时是否会引发递归线程饥饿,如何用 ManagedBlocker 缓解

ForkJoinTask.join() 在线程池饱和时是否会引发递归线程饥饿?如何用 ManagedBlocker 缓解?

  • join 阻塞
  • 递归饥饿
  • ManagedBlocker

ForkJoinTask.join() 在等待子任务完成时,若所有线程都在 join 等待(池饱和),可能引发递归线程饥饿:任务互相等待,无线程执行子任务,导致死锁状饥饿。缓解用 ManagedBlocker:ForkJoinPool 的 ManagedBlocker 允许阻塞任务在阻塞时临时增加池内线程(补充线程执行其他任务),避免阻塞任务占满池导致饥饿。用法:把阻塞操作包在 ManagedBlocker 的 block() 中,用 ForkJoinPool.managedBlock 调用。这样池能在阻塞时提供额外线程,防止 join 饥饿。

join 阻塞占满池会递归饥饿,ManagedBlocker 在阻塞时补线程解决。是 FJP 处理阻塞的关键。

#
★★★

6. Hystrix/Resilience4j 的线程池隔离与信号量隔离对比

Hystrix/Resilience4j 的线程池隔离与信号量隔离对比是什么?

  • 线程池隔离
  • 信号量隔离
  • 对比

线程池隔离:为每个依赖分配独立线程池,依赖占满只影响自己的池,完全隔离(隔离线程、超时、队列),但线程开销大、上下文切换多。信号量隔离:用信号量限制并发,无独立线程(复用调用线程),开销小、吞吐高,但依赖阻塞会占用调用线程(无法隔离超时)。对比:线程池隔离强隔离(适合慢/可能阻塞的依赖)、信号量隔离轻量(适合快/低延迟依赖)。Resilience4j 两者都支持,按依赖特性选择。

线程池隔离强但重,信号量隔离轻但弱。慢依赖用线程池,快依赖用信号量。

#
★★★

7. JDK 25 中 Executors.newVirtualThreadPerTaskExecutor() 与传统平台线程池的并发模型差异

JDK 25 中 Executors.newVirtualThreadPerTaskExecutor() 与传统平台线程池的并发模型差异是什么?

  • 虚拟线程 Executor
  • 平台线程池
  • 并发模型

newVirtualThreadPerTaskExecutor():每个任务创建一个新虚拟线程(无池化),线程廉价、阻塞时卸载载体线程,并发数无上限(受内存/载体限制),适合 IO 密集。传统平台线程池:固定/有限线程复用,避免线程创建开销,但线程数受核数限制,阻塞会浪费线程。差异:1) 虚拟线程不池化、可无限并发,平台线程池池化、有限并发;2) 虚拟线程阻塞卸载,平台线程阻塞占用;3) 虚拟线程适合 IO 密集(大量阻塞),平台线程池适合 CPU 密集或需限流。并发模型上,虚拟线程是"按任务建线程",平台线程池是"复用有限线程"。

虚拟线程 Executor 免池化、阻塞卸载,平台线程池池化复用、有限并发。IO 密集用虚拟线程,CPU 密集用平台池。

#
★★★

8. 线程池任务执行超时如何兜底,Future.get(timeout) 与任务自身取消在池内线程占用上的区别

线程池任务执行超时如何兜底?Future.get(timeout) 与任务自身取消在池内线程占用上的区别是什么?

  • 超时兜底
  • Future.get(timeout)
  • 线程占用

超时兜底:用 Future.get(timeout) 设置超时,超时抛 TimeoutException 并处理。区别:Future.get(timeout) 超时只是调用方放弃等待,任务本身仍在线程池线程上运行(可能继续执行),池内线程被占用直到任务完成;任务自身取消(任务内检查中断/超时并退出)才真正释放线程。因此 get(timeout) 超时后,若任务仍执行,线程未释放,需配合 cancel(true)(中断)或任务自身超时退出。线程占用:get(timeout) 不释放任务线程,任务内取消才释放。

get(timeout) 只放弃等待,不释放池线程;任务内响应取消/超时才释放。两者需配合。

#
★★★

9. 线程池队列使用 SynchronousQueue 与 LinkedBlockingQueue 在吞吐和背压上的差异,什么场景必须用 SynchronousQueue

线程池队列使用 SynchronousQueue 与 LinkedBlockingQueue 在吞吐和背压上的差异是什么?什么场景必须用 SynchronousQueue?

  • SynchronousQueue
  • LinkedBlockingQueue
  • 背压/吞吐

SynchronousQueue:无缓冲,任务必须直接交给工作线程(无则创建线程),生产者与消费者一对一交接,吞吐高但无排队缓冲,任务过多时立即拒绝/扩线程(newCachedThreadPool 用它)。LinkedBlockingQueue:有/无界缓冲,任务排队,生产者可先入队,背压较弱(有界队满才阻塞),吞吐受队列竞争影响。背压:SynchronousQueue 无缓冲,天然背压(当前无消费者则拒绝);LinkedBlockingQueue 有界缓冲,队满才背压。必须用 SynchronousQueue 的场景:需要"无缓冲、立即执行、快速失败"或"任务不缓存、直接交接"(如 cached 线程池、实时任务)。

SynchronousQueue 无缓冲强背压、即时交接,LinkedBlockingQueue 有缓冲柔性背压。实时/无缓冲场景用 SynchronousQueue。

#
★★★

10. 线程池线程饥饿与任务死锁的区分方法,任务内提交子任务到同一池时为何容易互相等待

线程池线程饥饿与任务死锁的区分方法?任务内提交子任务到同一池时为何容易互相等待?

  • 线程饥饿
  • 死锁
  • 同池提交子任务

线程饥饿:池线程被阻塞任务占满,等待任务无法获得线程,无循环等待;死锁:多个线程互相等待对方持有的锁/资源,有循环等待。区分:看线程 dump 是否呈"循环等待"链(死锁特征)或"线程全忙但无循环"(饥饿)。任务内提交子任务到同一池:若池线程已满,父任务占线程等待子任务,子任务在池中排队无线程执行,父任务与子任务互相等待(父等子、子等线程),形成类似死锁的饥饿/死锁。这是"线程池内嵌套提交"的经典陷阱。

同池嵌套提交导致"父任务等子任务、子任务等线程"的互相等待。应避免池内提交子任务或用独立池/虚拟线程。

#
★★★

11. MDC/TraceId 等基于 ThreadLocal 的上下文为何不会自动传到线程池任务中,如何用装饰器或 TransmittableThreadLocal 透传

MDC/TraceId 等基于 ThreadLocal 的上下文为何不会自动传到线程池任务中?如何用装饰器或 TransmittableThreadLocal 透传?

  • ThreadLocal 上下文
  • 线程池丢失
  • 透传方案

ThreadLocal 是"每线程一份",线程池任务在池线程执行,不会自动继承提交线程的 ThreadLocal(TraceId、MDC),导致日志断链。透传方案:1) 装饰器:提交时捕获提交线程的上下文,包装任务,执行时设置到池线程、执行后清理(finally);2) TransmittableThreadLocal(TTL):专门解决线程池传递的库,自动在提交/执行时传递 ThreadLocal,支持包装器。原理:把"线程本地"改为"任务本地",在任务边界捕获-设置-清理。

ThreadLocal 不跨线程池传递,需在任务边界捕获-设置-清理,TTL 封装了该机制。

#
★★★

12. Tomcat 容器线程池与业务线程池为何要做隔离,业务池阻塞或耗尽时如何反向影响 HTTP 请求处理

Tomcat 容器线程池与业务线程池为何要做隔离?业务池阻塞或耗尽时如何反向影响 HTTP 请求处理?

  • 容器线程池
  • 业务线程池隔离
  • 反向影响

隔离:Tomcat 容器线程池处理 HTTP 请求(连接、解析、响应),业务线程池执行业务逻辑。若业务逻辑直接在容器线程执行,业务阻塞/慢会占满容器线程,导致 HTTP 连接无法处理、请求排队、P99 恶化。隔离后:容器线程只负责接收与转发,业务在独立池执行,业务池耗尽不影响容器接收新请求(但请求会等待业务池)。反向影响:业务池耗尽时,请求在容器等待业务池线程,接入 RT 上升,若容器也有超时,请求被丢弃;业务池阻塞会传导到容器的有界连接池。隔离是防止"业务慢拖垮容器"。

容器池与业务池隔离,容器线程只做 IO 转发,业务池慢不直接占满容器。但业务池耗尽仍会传导排队。

#
★★★

13. 为不同业务配置独立线程池时,HTTP 请求线程池与后台任务线程池应当如何在隔离性、弹性与资源利用之间权衡

为不同业务配置独立线程池时,HTTP 请求线程池与后台任务线程池应如何在隔离性、弹性与资源利用之间权衡?

  • 独立线程池
  • 隔离性
  • 弹性/资源利用

独立线程池:隔离性好(一个业务慢/占满不影响其他),但每个池预留线程,资源利用低(总线程数多、闲置)。HTTP 请求线程池强调响应(低延迟、弹性),后台任务池强调吞吐(可容忍延迟)。权衡:1) 隔离性:关键业务独立池,避免被其他业务拖累;2) 弹性:用动态调参(调整 corePoolSize)或共享池+信号量适应波动;3) 资源利用:避免过多池导致线程闲置,可共享池+按优先级/隔离,或核心池用共享+信号量限流。平衡:最少化池数量,关键业务独立,非关键共享。

独立池换来隔离、牺牲资源利用。权衡是"关键业务隔离、非关键共享、动态弹性"。

#
★★★

14. 为线程池配置自定义 RejectedExecutionHandler 时,DB 限流场景下"调用方阻塞重试"与"CallerRunsPolicy"如何避免反向雪崩

为线程池配置自定义 RejectedExecutionHandler 时,DB 限流场景下"调用方阻塞重试"与"CallerRunsPolicy"如何避免反向雪崩?

  • RejectedExecutionHandler
  • 调用方阻塞
  • CallerRunsPolicy

池满拒绝时,自定义拒绝策略避免反向雪崩:1) CallerRunsPolicy:被拒任务由提交线程(调用方)直接运行,提交线程执行任务意味着它暂时不能提交新任务,自然减速(背压),避免继续洪峰;2) 调用方阻塞重试:提交被拒时提交线程阻塞等待(如自旋/睡眠再重试),也形成背压。两者都让"提交方"承担压力,把压力传导给上游,避免任务无限堆积与雪崩。DB 限流场景尤为关键:池满时不要让请求失控,用背压让调用方限速。

CallerRunsPolicy/调用方阻塞都是"把背压传导给调用方",防止拒绝后仍疯狂提交导致雪崩。

#
★★★

15. 线程池中任务抛出的异常与 rejected 任务分别记录在哪些位置,如何设计统一的失败追踪

线程池中任务抛出的异常与 rejected 任务分别记录在哪些位置?如何设计统一的失败追踪?

  • 任务异常记录
  • rejected 记录
  • 统一失败追踪

任务异常:execute 任务异常由线程的 UncaughtExceptionHandler 处理(或 finally 中记录);submit 任务异常封装在 Future 中,get 时抛 ExecutionException(需 get 或 whenComplete 记录)。rejected 任务:在 RejectedExecutionHandler 中记录(记录被拒的任务、原因、时间)。统一失败追踪:在任务包装器(装饰器)中统一捕获异常/超时/拒绝,记录到统一的日志/指标/告警系统,带任务上下文(TraceId、业务 ID)。设计:包装 Runnable 统一 try-catch + 拒绝策略统一记录 + 指标埋点。

异常在任务包装器/Handler 记录,拒绝在 RejectedHandler 记录,用统一包装器聚合追踪。

#
★★★

16. 线程池任务中执行阻塞 I/O 与计算任务混用时,线程数设定应以哪种任务为主,如何防止慢任务拖垮整体

线程池任务中执行阻塞 I/O 与计算任务混用时,线程数设定应以哪种任务为主?如何防止慢任务拖垮整体?

  • 混用任务
  • 线程数设定
  • 防慢任务

混用阻塞 I/O 与计算任务时,线程数设定应以"主要任务类型"为准:若以 I/O 为主,线程数按 I/O 公式(核数×等待比)放大;若以计算为主,按核数。但混用会导致阻塞任务占线程、计算任务排队。防慢任务拖垮整体:1) 分离:把阻塞 I/O 与计算任务分到不同池(阻塞池、计算池),互不拖累;2) 限时:给慢任务设超时;3) 限制:用信号量/队列限制阻塞任务并发;4) 区分优先级:慢任务低优先级。核心是"隔离阻塞与计算",避免慢任务占满线程。

混用阻塞与计算会让阻塞占线程。应分离池或限制,按主要任务类型定线程数。

#
★★★

17. 线程池任务执行顺序在 FIFO 队列下如何受线程数影响,批量任务的分批提交策略如何设计

线程池任务执行顺序在 FIFO 队列下如何受线程数影响?批量任务的分批提交策略如何设计?

  • FIFO 顺序
  • 线程数影响
  • 分批提交

FIFO 队列只保证"出队顺序",但多线程执行时,任务完成顺序不保证 FIFO(先进先出出队,但完成顺序取决于线程调度与执行时间)。单线程执行严格 FIFO,多线程并发执行完成顺序不定。批量任务分批提交策略:1) 按批次拆分(一批 N 个),控制并发;2) 用分区键分组(同一业务键提交到同一线程/队列,保证顺序);3) 结合 CompletableFuture 聚合批次结果;4) 控制批大小避免一次性提交过多。需保序时用单线程池或分区。

FIFO 保出队顺序不保完成顺序。保序需单线程或分区,分批提交控制粒度与并发。

#
★★★

18. 如何在大流量场景下避免线程池任务排队时 park/unpark 带来的隐性开销,从而减少 P99 延迟抖动

如何在大流量场景下避免线程池任务排队时 park/unpark 带来的隐性开销,从而减少 P99 延迟抖动?

  • park/unpark 开销
  • 排队
  • P99 抖动

线程池任务排队时,工作线程从队列取任务可能 park/unpark(队列空时 park,有任务时 unpark),高频的 park/unpark 有上下文切换与唤醒开销,造成 P99 抖动。避免:1) 减少队列空转(任务持续供给时避免线程 park);2) 用无锁/低开销队列(如 ConcurrentLinkedQueue 的 poll 非阻塞);3) 加大队列吞吐或批量取任务;4) 避免线程频繁 idle/唤醒(调整 keepAlive 与核心线程数);5) 用虚拟线程(无 park 开销,阻塞卸载)。核心是减少"空队列导致的 park/unpark 频率"。

park/unpark 抖动源于"队列空→线程 park→任务来→unpark"。减少空转与唤醒频率可稳 P99。

#
★★★

19. 如何评估 Tomcat 工作线程数与下游业务线程池、连接池容量的匹配关系,避免线程在等待中被占满

如何评估 Tomcat 工作线程数与下游业务线程池、连接池容量的匹配关系,避免线程在等待中被占满?

  • Tomcat 线程数
  • 业务线程池/连接池
  • 匹配关系

评估匹配:Tomcat 工作线程数决定并发 HTTP 请求数;每个请求消费一个业务池线程与一个连接池连接。若 Tomcat 线程数 > 业务池线程数,则部分请求在业务池排队;若业务线程数 > 连接池连接数,则线程在等连接。避免线程在等待中被占满:1) 保证层级容量匹配(Tomcat > 业务池 > 连接池 或按比例);2) 设置连接池获取超时,避免线程无限等连接;3) 监控各层线程/连接利用率,找瓶颈;4) 用虚拟线程替代 Tomcat 线程(无固定上限)。核心是"上游吞吐 ≤ 下游容量",避免线程阻塞在等待资源。

线程在等待中被占满 = 下游容量不足。需让各层容量匹配,设获取超时并用监控定位瓶颈。

#
★★★

20. 每个 Worker 持有一个 Thread 与一个 firstTask,引入可继承 ThreadLocal 在任务结束后未清理会产生什么后果

每个 Worker 持有一个 Thread 与一个 firstTask,引入可继承 ThreadLocal 在任务结束后未清理会产生什么后果?

  • Worker 模型
  • 可继承 ThreadLocal
  • 未清理后果

ThreadPoolExecutor 的 Worker 持有一个 Thread 与 firstTask(首任务)。若在任务中引入可继承 ThreadLocal(InheritableThreadLocal)或设置 ThreadLocal,任务结束后未清理(remove),池线程复用后,下一个任务会继承/读到上一个任务的 ThreadLocal 值,造成脏数据(上下文串扰、TraceId 错误、数据泄漏)。因为池线程长期存活并复用。后果:业务上下文混乱、日志关联错误、安全/data 泄漏。必须在任务结束清理(finally remove)或用装饰器。

池线程复用+ThreadLocal 未清理=脏数据泄漏。任务结束必须清理 ThreadLocal。

#
★★★

21. 监控线程池的活跃线程数与队列长度时,哪些 Prometheus 指标(thread_pool_*)最直接反映系统瓶颈

监控线程池的活跃线程数与队列长度时,哪些 Prometheus 指标最直接反映系统瓶颈?

  • Prometheus 指标
  • 活跃线程
  • 队列长度

反映线程池瓶颈的 Prometheus 指标:thread_pool_active(活跃线程数)、thread_pool_queue_size(队列长度)、thread_pool_current(当前线程数)、thread_pool_core/max(核心/最大线程数)、thread_pool_rejected(拒绝数)、thread_pool_completed(完成任务数)。最直接反映瓶颈:活跃线程数接近 max 且队列长度持续增长 → 线程池饱和,任务堆积;rejected 增加 → 拒绝。结合活跃率(active/max)与队列增长判断容量不足。

活跃线程接近最大值 + 队列增长 + 拒绝数 = 线程池瓶颈。监控活跃率与队列水位。

#
★★★

22. 线程池与信号量组合限流时,池内线程与信号量许可分别约束什么,双上限如何防止任务堆积

线程池与信号量组合限流时,池内线程与信号量许可分别约束什么?双上限如何防止任务堆积?

  • 线程池约束
  • 信号量约束
  • 双上限防堆积

线程池线程约束"并发执行的任务数"(线程数上限),信号量许可约束"允许进入的任务数"(准入控制)。双上限:信号量在提交前准入(acquire 不通过则不提交/阻塞),线程池则限制执行并发。组合后,信号量控制"待处理任务的准入量",线程池控制"执行并发量",双上限防止任务无限堆积:即使线程池队列满,信号量也会阻止更多任务进入,避免堆积。信号量是"入口闸门",线程池是"执行闸门"。

信号量管"准入",线程池管"执行",双上限让任务不会无限堆积(入口截流)。

#
★★★

23. 突发流量下任务提交路径的判定顺序如何决定实际并发上限

突发流量下任务提交路径的判定顺序如何决定实际并发上限?

  • 提交路径
  • 判定顺序
  • 并发上限

ThreadPoolExecutor 提交判定顺序:1) 若工作线程数 < corePoolSize,创建线程执行;2) 否则尝试加入队列(workQueue.offer);3) 若队列满,若线程数 < maxPoolSize,创建线程执行;4) 否则走拒绝策略。判定顺序决定实际并发上限:核心线程 + 队列 + (max-核心)额外线程。突发流量下,先占核心线程,再进队列,队列满才扩线程到 max,仍满则拒绝。因此"实际并发上限" = min(max, 核心+满队列可承载),队列越大,越晚扩线程(线程数越少),任务堆积越多。

提交顺序(核心→队列→max→拒绝)决定并发与排队。队列越大越晚扩线程,堆积越多。

#
★★★

24. 线程池队列容量与拒绝策略如何设计以形成背压,队列过长与过短的成本如何权衡?

线程池队列容量与拒绝策略如何设计以形成背压?队列过长与过短的成本如何权衡?

  • 队列容量
  • 拒绝策略
  • 背压

背压设计:有界队列 + 明确拒绝策略(或 CallerRunsPolicy),队满时拒绝/调阻塞,形成背压传导给上游。队列过长成本:任务堆积、延迟增大、内存占用、难以及时发现故障(掩盖问题);队列过短成本:频繁拒绝、扩线程、吞吐下降、波动大。权衡:队列容量应"吸收短时波动"而不"长期堆积";配合拒绝策略(Abort 快速失败、CallerRuns 背压、Discard 丢弃)。理想:队列容量约等于"峰值吞吐×可接受等待时长",超限即拒绝告警。

队列是"缓冲垫",容量要吸收波动但不掩盖故障。拒绝策略决定背压方式(快速失败/调用方执行)。

#
★★★

25. StructuredTaskScope(JEP 453)在多虚拟线程任务编排中如何保证子任务异常传播与整体失败语义

StructuredTaskScope(JEP 453)在多虚拟线程任务编排中如何保证子任务异常传播与整体失败语义?

  • StructuredTaskScope
  • 异常传播
  • 失败语义

StructuredTaskScope(JEP 453)用作用域管理子任务:fork 子任务在作用域内运行,join 等待所有子任务。异常传播:子任务异常被捕获,通过策略(ShutdownOnFailure 等)决定整体失败——任一子任务失败时,级联取消其他子任务并让整体失败(抛出异常)。整体失败语义:要么全部成功,要么按策略整体失败(短路),避免"部分成功部分失败"的模糊状态。子任务异常聚合到父作用域统一处理。这保证多虚拟线程编排的失败一致性。

SScope 用"策略+级联取消"保证整体失败语义与异常聚合,是结构化并发处理失败的核心。

#
★★

26. 线程池与协程(虚拟线程)的取舍

线程池与协程(虚拟线程)的取舍是什么?

  • 线程池
  • 虚拟线程
  • 取舍

线程池:复用平台线程,控制并发上限,适合 CPU 密集、需限流/隔离、线程生命周期管理的场景;但线程数受限、阻塞浪费线程。虚拟线程:廉价、无池化、阻塞卸载,适合 IO 密集、大量并发阻塞的场景;但无法精确限流(并发数无上限)、CPU 密集无收益。取舍:IO 密集用虚拟线程(免池化、高并发),CPU 密集或需限流隔离用线程池;两者可混用(虚拟线程 + 信号量限流)。虚拟线程简化 IO 密集,线程池保留控制能力。

虚拟线程解决阻塞并发,线程池保留池化/限流控制。IO 密集虚拟线程,CPU 密集/限流线程池。

#
★★

27. 结构化并发 vs 虚拟线程 vs 响应式

结构化并发 vs 虚拟线程 vs 响应式是什么?

  • 结构化并发
  • 虚拟线程
  • 响应式

结构化并发(StructuredTaskScope):以作用域组织并发任务,结构化生命周期、异常聚合、级联取消,强调"任务结构与调用结构一致"。虚拟线程:廉价轻量线程,解决阻塞并发的资源问题,是结构化并发的载体。响应式(Reactive,如 Reactor/RxJava):异步回调/流式编程,无阻塞、背压,适合高并发 IO 但代码复杂、回调地狱。三者关系:虚拟线程是"线程"层面,结构化并发是"编排"层面,响应式是"异步模型"层面。实践:虚拟线程+结构化并发可替代部分响应式(同步代码+高并发),响应式仍适合高吞吐流式场景。

虚拟线程解决资源,结构化并发解决编排,响应式是异步模型。虚拟线程+结构化并发是响应式的现代替代。

#
★★

28. JDK 21 引入虚拟线程后,传统 ThreadPool 的"池大小 = CPU 数 + I/O 系数"经验公式在哪些场景下仍成立,哪些场景应彻底切换

JDK 21 引入虚拟线程后,传统 ThreadPool 的"池大小 = CPU 数 + I/O 系数"经验公式在哪些场景仍成立,哪些场景应彻底切换?

  • 经验公式
  • 虚拟线程
  • 适用边界

经验公式(CPU 密集≈核数,IO 密集≈核数×系数)适用于平台线程池。虚拟线程后:IO 密集场景应彻底切换(虚拟线程无需池化、阻塞卸载、无公式限制),CPU 密集场景公式仍成立(虚拟线程在 CPU 密集无卸载收益,仍受核数限制,用平台线程池或虚拟线程公式核数)。即:IO 密集 → 虚拟线程(公式作废);CPU 密集 → 仍按核数定线程数(公式成立)。混合场景按主要类型。

虚拟线程使 IO 密集的池大小公式作废,CPU 密集公式仍适用(受核数限制)。

#
★★

29. 线程池的七参数与执行流程,核心线程、队列、最大线程、拒绝策略如何组合?

线程池的七参数与执行流程:核心线程、队列、最大线程、拒绝策略如何组合?

  • 七参数
  • 执行流程
  • 组合

ThreadPoolExecutor 七参数:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(非核心线程存活时间)、unit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)、handler(拒绝策略)。执行流程:1) 线程数 < core 则创建核心线程;2) 否则入队;3) 队列满且线程数 < max 则创建非核心线程;4) 仍满则拒绝。组合:核心线程处理常规,队列缓冲波动,非核心线程应对突发,拒绝策略兜底。按业务配置:CPU 密集小队列、IO 密集调整线程数。

七参数组合决定"核心+队列+弹性+兜底"的完整执行模型。理解流程才能正确配置。

#
★★

30. 如何监控线程池状态(活跃线程/队列积压/拒绝次数)并动态调整参数?

如何监控线程池状态(活跃线程/队列积压/拒绝次数)并动态调整参数?

  • 监控状态
  • 动态调参
  • 指标

监控:用 ThreadPoolExecutor 的 getActiveCount/getQueue().size()/getTaskCount() + 自定义埋点(活跃线程、队列积压、拒绝次数、任务耗时),暴露为 Prometheus/Micrometer 指标。动态调参:setCorePoolSize/setMaximumPoolSize/setKeepAliveTime 动态调整,配合队列容量。策略:根据监控(活跃率>80%、队列持续增长)动态扩 corePoolSize;空闲时缩。结合超时/告警。动态调整能应对流量波动,避免固定参数。

监控(活跃/队列/拒绝)+ 动态 set 参数,实现自适应线程池。指标驱动调参。

#
★★

31. 线程池与 Thread.UncaughtExceptionHandler 配合时,为什么任务内吞掉的异常不会触发 Handler,如何通过包装 Runnable 解决

线程池与 Thread.UncaughtExceptionHandler 配合时,为什么任务内吞掉的异常不会触发 Handler?如何通过包装 Runnable 解决?

  • UncaughtExceptionHandler
  • 吞异常
  • 包装 Runnable

UncaughtExceptionHandler 只处理"未捕获的异常"(run 方法抛出且未被 catch)。若任务内自己 try-catch 吞掉了异常,Handler 不会触发。线程池中,submit 任务异常被封装进 Future(不触发 Handler),execute 任务异常才由线程的 Handler 处理。解决:用包装 Runnable 统一捕获异常(try-catch 记录/上报),或不用 submit 的吞异常,用装饰器在任务边界统一 catch 并记录,确保异常不静默。

Handler 只接未捕获异常,任务内吞掉不触发。包装 Runnable 统一 catch 记录防静默。

#
★★

32. 线程池参数动态调整(setCorePoolSize 等)

线程池参数动态调整(setCorePoolSize 等)是什么?

  • setCorePoolSize
  • 动态调整
  • 语义

setCorePoolSize(int):动态调整核心线程数。调大时若当前线程数 < 新 core,会创建线程;调小时若线程数 > 新 core,多余线程在空闲超时后回收(keepAlive)。setMaximumPoolSize、setKeepAliveTime 也可动态调整。语义:调整是"平滑"的,不立即中断线程,多余线程按 keepAlive 超时回收。动态调整用于应对流量波动(高峰期扩核心、低谷缩),但注意 setCorePoolSize 与队列的交互(若队列满且线程 < max,调大 core 会立即建线程执行)。

setCorePoolSize 平滑调整核心线程,扩即时建、缩按 keepAlive 回收。用于动态容量管理。

#
★★

33. 线程池参数调优的工程经验(CPU 密集/IO 密集公式)

线程池参数调优的工程经验(CPU 密集/IO 密集公式)是什么?

  • CPU 密集公式
  • IO 密集公式
  • 调优经验

工程经验:CPU 密集线程数 ≈ 核数 + 1(避免切换);IO 密集 ≈ 核数 × (1 + 等待时间/执行时间)(放大等待)。队列:CPU 密集用有界小队列(SynchronousQueue/小容量),IO 密集用有界较大队列吸收波动。调优流程:先按公式初设,再压测调整(观察活跃率、队列、吞吐、P99),结合监控动态调。避免过度调大(内存/切换)、调小(吞吐不足)。虚拟线程下 IO 密集免调参。

公式是起点,压测调优是终点。CPU 密集≈核数,IO 密集按等待比放大,配合有界队列。

#
★★

34. 线程池在 Spring TaskExecutor 中的配置

线程池在 Spring TaskExecutor 中的配置是什么?

  • Spring TaskExecutor
  • 线程池配置
  • 集成

Spring 的 TaskExecutor 抽象了线程池,常用 ThreadPoolTaskExecutor 实现(包装 ThreadPoolExecutor)。配置:corePoolSize、maxPoolSize、queueCapacity、keepAliveSeconds、threadNamePrefix、拒绝策略(RejectedExecutionHandler)、allowCoreThreadTimeOut。Spring 中通过 @Bean 配置 ThreadPoolTaskExecutor,或启用 @Async 使用它执行异步方法。还可用 ThreadPoolExecutorFactoryBean 配置原生的 ThreadPoolExecutor。要点:配置与原生一致,但 queueCapacity 与 maxPoolSize 交互需注意(Spring 的 queueCapacity 默认无界)。

Spring ThreadPoolTaskExecutor 包装原生线程池,配置核心/最大/队列/前缀/拒绝。@Async 依赖它。

#
★★

35. 线程池的 RejectedExecutionHandler 自定义日志收集

线程池的 RejectedExecutionHandler 自定义日志收集是什么?

  • RejectedExecutionHandler
  • 自定义
  • 日志收集

自定义 RejectedExecutionHandler 实现 rejectedExecution(Runnable r, ThreadPoolExecutor e),在任务被拒时执行自定义逻辑:记录被拒任务(任务类型、提交时间、线程池状态)、收集到日志/指标/告警、决定降级(丢弃/重试/调用方执行)。要点:记录线程池状态(活跃/队列/当前线程)便于诊断;避免在 handler 内做重操作(阻塞、IO);与 Metrics 集成记录拒绝次数。日志收集为排障提供依据。

自定义拒绝策略记录被拒任务与池状态,集成日志/指标,是管理拒绝的关键。

#
★★

36. 线程池的 Worker 模型与任务执行流程

线程池的 Worker 模型与任务执行流程是什么?

  • Worker 模型
  • 任务执行流程
  • runWorker

ThreadPoolExecutor 的 Worker 是内部类,持有一个 Thread 与 firstTask。Worker 启动后执行 runWorker:循环从队列取任务(getTask),执行任务(beforeExecute → task.run → afterExecute),捕获异常;无任务时按 keepAlive 回收(非核心)。Worker 模型:每个 Worker 对应一个池线程,线程池通过 Worker 集合管理线程生命周期。任务执行流程:核心 Worker 先执行 firstTask,再循环取队列任务,队列空且允许则退出。

Worker 把"线程+任务"封装,runWorker 循环取任务执行。是线程池执行的核心模型。

#
★★

37. 线程池的 Worker 模型在 setCorePoolSize 动态调整时的锁竞争与可见性如何保证

线程池的 Worker 模型在 setCorePoolSize 动态调整时的锁竞争与可见性如何保证?

  • Worker 模型
  • 动态调整
  • 锁竞争/可见性

ThreadPoolExecutor 用 mainLock(ReentrantLock)保护 Worker 集合与状态(runState、workerCount)。setCorePoolSize 等动态调整操作会获取 mainLock 修改 workerCount/状态,保证可见性(volatile workerCount + 锁)。规模较大时,mainLock 上的竞争(addWorker/removeWorker/调整)可能成为热点,但通常操作低频。可见性保证:workerCount 用 AtomicInteger(CAS),状态用 volatile,关键操作持锁。动态调整时锁保证"调整与线程创建/回收"的一致性。

mainLock 保护 Worker 集合与状态,workerCount 用原子/volatile 保证可见性。动态调整持锁保证一致性。

#
★★

38. 线程池的 allowCoreThreadTimeOut 在 core 线程空闲超过 keepAliveTime 后被回收,对长生命周期任务池的设计取舍是什么

线程池的 allowCoreThreadTimeOut 在 core 线程空闲超过 keepAliveTime 后被回收,对长生命周期任务池的设计取舍是什么?

  • allowCoreThreadTimeOut
  • 核心线程回收
  • 取舍

allowCoreThreadTimeOut(true) 允许核心线程空闲超过 keepAliveTime 后被回收(配合 keepAlive),使池可收缩到 0。取舍:好处:空闲时回收线程、省资源;坏处:长生命周期任务池若频繁出现空闲-创建,线程反复创建/销毁,有开销,且突发流量时核心线程被回收导致首次任务延迟。设计取舍:长生命周期任务池(持续有任务)应保持核心线程(allowCoreThreadTimeOut=false),避免频繁重建;波动大的场景可开启回收省资源。需权衡资源节省与线程重建成本。

allowCoreThreadTimeOut 让核心线程可回收,省资源但基线延迟。持续任务池宜保持核心线程。

#
★★

39. 线程池的 execute 与 submit 差异

线程池的 execute 与 submit 差异是什么?

  • execute
  • submit
  • 差异

execute(Runnable):提交任务,无返回值,适用 fire-and-forget;异常直接由线程处理(UncaughtExceptionHandler)。submit(Runnable/Callable):返回 Future,可获取结果/异常(get 抛 ExecutionException)或取消;Callable 返回结果。差异:submit 有 Future、能捕获异常、能取消;execute 无返回值、异常由线程处理。若 submit 不调 get,异常被吞(装进 Future)。需要结果/管理用 submit,纯执行用 execute。

execute 无返回值、异常线程处理;submit 返回 Future、可捕获异常与取消。按需选择。

#
★★

40. 线程池的线程名前缀(ThreadFactory)与监控埋点

线程池的线程名前缀(ThreadFactory)与监控埋点是什么?

  • ThreadFactory
  • 线程名前缀
  • 监控埋点

ThreadFactory 定制线程创建:设置线程名前缀(如业务名-池名)、daemon、优先级、UncaughtExceptionHandler。线程名前缀的价值:线程 dump/日志/监控中识别归属,便于排障。监控埋点:在 ThreadFactory 中或任务包装器中埋点(线程数、任务耗时、异常),暴露指标。配合 Prometheus/Micrometer 监控线程池。实现:自定义 ThreadFactory 统一命名+埋点,提升可观测性。

ThreadFactory 统一命名(可观测性)+ 埋点(监控)。线程名前缀是生产排障的基础。

#
★★

41. 线程池的预热(prestartCoreThread)与按需扩容

线程池的预热(prestartCoreThread)与按需扩容是什么?

  • 预热
  • prestartCoreThread
  • 按需扩容

prestartCoreThread() 预启动一个核心线程(无任务也运行),prestartAllCoreThreads() 预启动所有核心线程。预热的价值:避免首次任务到来时创建线程的延迟(首请求延迟),尤其对延迟敏感业务。按需扩容:默认线程池按需创建核心线程(首个任务触发),预热则提前创建。取舍:预热省首次延迟但空闲占线程;按需扩容省资源但首任务延迟。预热用于已知持续有任务、延迟敏感的场景。

预热(prestart)提前建线程省首任务延迟,按需扩容省资源。延迟敏感场景预热。

#
★★

42. 线程池监控 MXBean.getThreadPool* 指标在告警阈值设定的工程经验有哪些

线程池监控 MXBean.getThreadPool* 指标在告警阈值设定的工程经验有哪些?

  • MXBean 指标
  • 告警阈值
  • 经验

ThreadPoolExecutor 的 MXBean 指标:getActiveCount、getPoolSize、getQueue().size、getTaskCount、getCompletedTaskCount、getRejectedExecutionCount(自定义)。告警阈值经验:1) 活跃线程率(active/max)> 80% 持续告警(接近饱和);2) 队列积压 > 容量的 70-80% 告警(堆积风险);3) 拒绝次数 > 0 立即告警(容量不足);4) 任务耗时 P99 超 SLA 告警。阈值需结合流量基线,避免误报;用"持续 N 分钟"去抖。结合宏观(线程满+队列长)判断瓶颈。

告警阈值:活跃率>80%、队列>70%、拒绝>0、耗时破 SLA。结合基线与去抖避免误报。

#
★★

43. 线程池隔离(业务隔离池)与任务混合的取舍

线程池隔离(业务隔离池)与任务混合的取舍是什么?

  • 隔离池
  • 任务混合
  • 取舍

隔离池:为不同业务分配独立线程池,隔离故障(一个慢/占满不影响其他),但资源利用低(线程闲置)。任务混合:共享一个池,资源利用高,但一个慢任务/异常任务可能占满池,拖累其他业务(级联)。取舍:关键业务(SLA 高、易阻塞)用隔离池,非关键/低风险业务共享;混合池需用优先级、超时、限流保护。核心是"故障隔离 vs 资源利用"的平衡,一般关键业务隔离、普通业务共享。

隔离池保故障隔离、共享池提资源利用。关键业务隔离、普通共享。

#
★★

44. 线程池中任务导致 StackOverflowError 时该 Worker 线程会怎样,线程池如何继续服务其他任务

线程池中任务导致 StackOverflowError 时该 Worker 线程会怎样?线程池如何继续服务其他任务?

  • StackOverflowError
  • Worker 线程
  • 线程池恢复

任务抛出 StackOverflowError(Error 而非 Exception)时,runWorker 会捕获 Throwable(调用 afterExecute 记录)后重新抛出,该 Worker 线程随之异常退出 while 循环而终止(completedAbruptly=true)。随后 processWorkerExit 会移除该 Worker;若线程池仍处于运行状态且有任务,会补充新的 Worker 线程继续服务其他任务。注意:Error 级别(StackOverflowError、OOM 等)可能破坏线程栈或 JVM 状态,若任务反复抛出同类 Error,线程池会不断重建线程,应定位根因,而不能只依赖自动补充。

runWorker 对 Throwable 是"捕获记录后重新抛出",Worker 线程随异常退出而终止;线程池通过 processWorkerExit 移除该 Worker 并视情况补充新线程,从而继续服务其他任务。Error 可能破坏状态,需警惕并排查根因。

#
★★

45. 线程池任务的取消与中断传播,shutdownNow 如何中断空闲与运行中线程,任务应如何响应中断

线程池任务的取消与中断传播:shutdownNow 如何中断空闲与运行中线程?任务应如何响应中断?

  • shutdownNow
  • 中断
  • 响应

shutdownNow() 会:1) 中断所有线程(含空闲与运行中),调用 Thread.interrupt();2) 返回未执行的任务列表(队列中未运行的任务)。空闲线程被中断后,从队列取任务时(getTask 检查中断)退出;运行中线程被中断,若任务响应中断则停止,否则继续。任务应响应中断:检查 interrupt 标志、捕获 InterruptedException 并停止/清理。shutdownNow 只发中断信号,真正停止依赖任务协作。

shutdownNow 中断所有线程+返回未执行任务。任务需响应中断才能停止,否则继续运行。

#
★★

46. 线程池任务的延迟执行与超时取消,schedule 与 submit 组合实现定时提交的边界

线程池任务的延迟执行与超时取消:schedule 与 submit 组合实现定时提交的边界是什么?

  • schedule
  • submit 组合
  • 定时边界

ScheduledThreadPoolExecutor 的 schedule 提供延迟执行(delay 后执行一次)。schedule 与 submit 组合:schedule 提交延迟任务,返回 ScheduledFuture;submit 提交立即任务。定时提交的边界:schedule 只做"延迟执行一次",周期任务用 scheduleAtFixedRate/scheduleWithFixedDelay;若要"定时后取消",用 ScheduledFuture.cancel 或 Future.get(timeout) 处理超时。边界:schedule 是延迟,延时队列(DelayQueue)保证到期取执行;超时取消需配合 get(timeout)/cancel。

schedule 延迟执行,周期用 scheduleAtFixedRate/FixedDelay,超时取消用 cancel/get(timeout)。

#
★★

47. 线程池任务粘滞性,同一业务键的任务如何保证顺序执行,分区队列与单线程 worker 的实现取舍

线程池任务粘滞性:同一业务键的任务如何保证顺序执行?分区队列与单线程 worker 的实现取舍是什么?

  • 任务粘滞性
  • 顺序执行
  • 分区队列/单线程 worker

任务粘滞性:同一业务键(如订单号)的任务需按提交顺序执行。方案:1) 分区队列:按业务键哈希到多个队列,每个队列由单线程 worker 消费,保证同键顺序;2) 单线程 worker:每个业务键一个专用线程(或"分片+一个线程"),保证同键顺序。取舍:分区队列可并行(不同分区不同线程)但需哈希均匀;单线程 worker 简单但一个键串行(吞吐受限)。实现:用 keyed executor(如按 key 取 executor)或分片+轮询。保证同键顺序、不同键并行。

同键顺序执行靠"分区+单线程消费"或"按 key 分派线程"。分区提升并行,单线程保序。

#
★★

48. 线程池任务执行中的锁等待时间如何统计,JFR 的 jdk.JavaMonitorEnter 事件如何定位池内锁竞争

线程池任务执行中的锁等待时间如何统计?JFR 的 jdk.JavaMonitorEnter 事件如何定位池内锁竞争?

  • 锁等待统计
  • JFR 事件
  • 锁竞争定位

锁等待时间统计:用 JFR 的 jdk.JavaMonitorEnter 事件(记录线程进入 monitor 的等待时间/阻塞时长)、jdk.JavaMonitorBlocked 事件,或 ThreadMXBean 的 getThreadInfo 拿 blocked time。JFR 的 JavaMonitorEnter 事件记录线程等待锁的时长与锁对象,可用于定位池内锁竞争:哪些线程在哪个锁上等待最久、锁竞争热点。结合任务包装器记录任务耗时与锁等待,定位瓶颈。JFR 低开销、适合生产。

JFR JavaMonitorEnter 提供锁等待时长与锁对象,定位池内锁竞争热点。

#
★★

49. 线程池的队列容量与任务丢弃策略在削峰场景如何配合,丢弃任务的补偿机制如何设计

线程池的队列容量与任务丢弃策略在削峰场景如何配合?丢弃任务的补偿机制如何设计?

  • 队列容量
  • 丢弃策略
  • 削峰补偿

削峰场景:队列容量吸收峰值(缓冲),丢弃策略在超限时处理。配合:有界队列 + 明确丢弃策略(Abort/Discard/CallerRuns),峰值时队列吸收、超限丢弃。补偿机制:1) 丢弃前记录到 MQ/消息队列,异步补处理;2) 记录到数据库重试队列,后台重试;3) 丢弃时返回降级响应,业务侧重试;4) 记录指标告警。核心:丢弃不丢失业务(落库/队列补偿),用异步补偿保证最终一致。

削峰 = 队列缓冲 + 丢弃兜底 + 异步补偿(MQ/重试队列),保证丢弃后能恢复。

#
★★

50. 线程池任务依赖外部服务时,池内线程等待下游超时会导致什么连锁问题,如何用并行度与超时上限保护

线程池任务依赖外部服务时,池内线程等待下游超时会导致什么连锁问题?如何用并行度与超时上限保护?

  • 下游超时
  • 连锁问题
  • 并行度/超时保护

池内线程等待下游超时会:1) 线程被慢调用占满,池线程耗尽;2) 新任务排队,队列积压;3) 下游雪崩(线程都在等慢服务),连锁拖垮整个池。保护:1) 设下游调用超时(ConnectTimeout/ReadTimeout),避免无限等待;2) 限制并行度(信号量/隔离池),防集中依赖;3) 熔断(下游故障时快速失败);4) 队列有界+丢弃策略。核心是"超时上限 + 并行度控制 + 熔断",防止慢下游拖垮线程池。

慢下游会占满池线程导致雪崩。超时+并行度+熔断三管齐下保护。

#
★★

51. 线程池线程的 CPU 亲和性绑定(Thread Affinity)在 Java 中的可行性,虚拟线程下为何不再适用

线程池线程的 CPU 亲和性绑定(Thread Affinity)在 Java 中的可行性?虚拟线程下为何不再适用?

  • CPU 亲和性
  • Java 可行性
  • 虚拟线程

Java 标准库不提供 CPU 亲和性 API,线程亲和性需通过 JNI/native 调用(pthread_setaffinity_np、sched_setaffinity)或第三方库实现,受平台限制且复杂。平台线程池线程理论上可绑定 CPU,但实现成本高。虚拟线程下不适用:虚拟线程在载体线程间动态迁移(1:N 调度),不固定绑定具体平台线程,无法绑定 CPU;且虚拟线程是轻量任务,亲和性无意义。因此 CPU 亲和性在 Java 平台线程上可行但复杂,虚拟线程下不可用。

Java 亲和性需 native 实现,虚拟线程因迁移特性无法绑定 CPU。

#
★★

52. AbstractExecutorService 的 submit 默认实现

AbstractExecutorService 的 submit 默认实现是什么?

  • AbstractExecutorService
  • submit 实现
  • newTaskFor

AbstractExecutorService 提供 submit 的默认实现:把 Runnable/Callable 用 newTaskFor 包装成 FutureTask(RunnableFuture),然后 execute(futureTask) 提交,返回该 FutureTask。newTaskFor 是可覆盖的钩子(可自定义 FutureTask 实现)。流程:submit(Runnable) → newTaskFor 包装 → execute → 返回 Future。子类(如 ThreadPoolExecutor)只需实现 execute 与 newTaskFor,submit 逻辑复用。

submit 默认用 newTaskFor 包装 FutureTask 再 execute,返回 Future。newTaskFor 可定制。

#
★★

53. BlockingDeque(LinkedBlockingDeque)的工作窃取应用

BlockingDeque(LinkedBlockingDeque)的工作窃取应用是什么?

  • BlockingDeque
  • 工作窃取
  • 应用

LinkedBlockingDeque 是双向阻塞队列,支持两端操作,可用于工作窃取:多个 worker 各自从自己的 deque 取任务,若自己的 deque 空,从其他 worker 的 deque 队尾(或队头)窃取任务,实现负载均衡。JDK 的 ForkJoinPool 工作窃取就用类似双端队列(不过它用自定义 WorkQueue)。LinkedBlockingDeque 作为通用双端队列,可为多 worker 场景实现"自己的队头取、窃取队尾"的窃取模式,减少竞争、均衡负载。

双端队列支持工作窃取(本地队头取、窃取队尾),均衡负载。LinkedBlockingDeque 是通用实现。

#
★★

54. 线程池中任务的批量分片执行,FixedThreadPool 与分片游标如何配合实现并行消费

线程池中任务的批量分片执行:FixedThreadPool 与分片游标如何配合实现并行消费?

  • 批量分片
  • FixedThreadPool
  • 分片游标

批量分片执行:把大任务集(如 100 万条记录)按固定大小分片(如每片 1000 条),用分片游标(AtomicInteger 或计数器)记录当前处理到的片索引,多个线程用 CAS 递增游标取下一片,并行消费。FixedThreadPool 提供固定并发,配合游标保证"每片只被消费一次、无重复"。实现:游标.getAndIncrement 取片号,线程池并行处理各片。配合分片游标实现无锁的并行分片消费。

分片游标(原子递增)+ FixedThreadPool 并行消费,CAS 保证每片不重复。

#
★★

55. 线程池饱和时的降级路径设计,消息队列缓冲、同步降级与快速失败各自适用什么场景

线程池饱和时的降级路径设计:消息队列缓冲、同步降级与快速失败各自适用什么场景?

  • 降级路径
  • 消息队列缓冲
  • 同步降级/快速失败

饱和降级:1) 消息队列缓冲:池满时任务入 MQ,异步消费,适合可异步、需削峰、容忍延迟的场景;2) 同步降级:池满时返回降级结果(缓存/默认值),适合可接受降级、需实时响应的场景;3) 快速失败:池满即抛异常/拒绝,适合关键任务、不能延迟、需快速感知的场景。选择:按任务特性(能否异步、能否降级、延迟敏感性)。消息队列缓冲保吞吐、降级保响应、快速失败保即时感知。

三种降级路径按"是否可异步/可降级/延迟敏感"选择。MQ 缓冲、降级保响应、快速失败保感知。

#
★★

56. CompletionService(ExecutorCompletionService)的批量提交

CompletionService(ExecutorCompletionService)的批量提交是什么?

  • CompletionService
  • ExecutorCompletionService
  • 批量

ExecutorCompletionService 包装一个 Executor,提交任务后,已完成的任务按完成顺序放入阻塞队列(take 取最早完成的)。批量提交:提交多个任务,用 take() 按完成顺序消费结果,先完成先处理,避免逐个 get 阻塞等待最慢的。适合"批量并行 + 先完成先处理"(如聚合多个服务的响应)。语法:queue.submit(task) 提交,queue.take() 取完成结果。相比 Future 列表,CompletionService 按完成顺序返回,提升吞吐。

CompletionService 按完成顺序 take 结果,先完成先处理,适合批量并行聚合。

#
★★

57. 线程池的线程数调整在 Kubernetes 容器 CPU 限额下的实践,可用处理器数如何被容器感知并影响默认并行度

线程池的线程数调整在 Kubernetes 容器 CPU 限额下的实践:可用处理器数如何被容器感知并影响默认并行度?

  • 容器 CPU 限额
  • 可用处理器
  • 默认并行度

在线程数调整时,Java 默认用 Runtime.availableProcessors() 计算并行度(如 commonPool、默认线程数),但容器早期该值返回宿主机核数而非容器配额,导致线程数虚高。JDK 10+ 通过容器感知(cgroup)修正 availableProcessors 返回容器配额。实践:1) 确认 JDK 版本支持容器感知(JDK 10+);2) 线程数按容器配额核算(CPU 密集≈配额核数);3) 监测容器 CPU 限制,避免线程数超过配额导致 CPU 节流;4) 用 -XX:ActiveProcessorCount 显式指定。K8s 下按容器配额定线程数。

容器感知让 availableProcessors 返回配额,线程数按配额核算,避免超限节流。

#
★★

58. ExecutorService.invokeAll/invokeAny 的批量任务

ExecutorService.invokeAll/invokeAny 的批量任务是什么?

  • invokeAll
  • invokeAny
  • 批量

invokeAll(Collection):提交所有任务并等待全部完成,返回 Future 列表(按提交顺序),任一任务异常会抛(但其他任务继续)。invokeAny(Collection):提交所有任务,返回最先完成(成功)的结果,其他任务被取消(或继续)。差异:invokeAll 等全部、invokeAny 取最快。批量场景:invokeAll 聚合全部结果,invokeAny 取最快响应(如多源取最快)。都支持 timeunit 超时版本。

invokeAll 等全部结果,invokeAny 取最快成功。批量任务的两种聚合语义。

#
★★

59. ExecutorService.shutdownNow 返回的 Runnable 列表用途

ExecutorService.shutdownNow 返回的 Runnable 列表用途是什么?

  • shutdownNow
  • Runnable 列表
  • 用途

shutdownNow() 返回"队列中未开始执行的任务"列表(Runnable),用途:1) 优雅停机时,把未执行任务转移到新线程池/持久化,避免丢失;2) 记录未完成任务,便于补偿重试;3) 分析停机时积压的任务量。注意:返回的是未执行任务,运行中的任务不在此列(但会被中断)。用途核心是"停机时保留未完成任务,用于补偿/续跑"。

shutdownNow 返回未执行任务,用于停机时补偿/迁移,避免任务丢失。

#
★★

60. ForkJoinPool 在 JDK 21+ 中支持虚拟线程模式后,CPU-bound 任务与 I/O-bound 任务的并行策略是否有本质变化

ForkJoinPool 在 JDK 21+ 中支持虚拟线程模式后,CPU-bound 任务与 I/O-bound 任务的并行策略是否有本质变化?

  • ForkJoinPool 虚拟线程
  • CPU-bound
  • I/O-bound

JDK 21+ ForkJoinPool 的虚拟线程模式(线程工厂用虚拟线程)使池内任务在线程上运行,I/O-bound 阻塞时虚拟线程卸载,不占池线程。并行策略变化:CPU-bound 任务仍受池并行度(核数)限制,无本质变化(虚拟线程 CPU 密集无卸载收益);I/O-bound 任务因虚拟线程卸载,池内"阻塞任务"不再占线程,可同时运行更多 I/O 任务,并行策略更灵活。本质:CPU-bound 仍受核数,I/O-bound 因虚拟线程卸载而提升并发,策略调整主要针对 I/O-bound。

虚拟线程模式主要改善 I/O-bound(阻塞卸载),CPU-bound 仍受核数限制,无本质变化。

#
★★

61. ForkJoinPool 在 Stream 并行流中的角色

ForkJoinPool 在 Stream 并行流中的角色是什么?

  • ForkJoinPool
  • 并行流
  • 角色

Stream 并行流(parallelStream)默认使用 ForkJoinPool.commonPool(公共池)执行并行任务。commonPool 的并行度 = 核数-1,任务通过工作窃取(work-stealing)调度。角色:commonPool 为并行流提供并行执行环境、负载均衡。局限:commonPool 被阻塞任务占满会拖累其他并行流;可用自定义 ForkJoinPool(ForkJoinPool.commonPool 之外)配合 --parallelism 或自定义流执行。理解角色:并行流的并行度与执行由 commonPool 决定。

并行流默认跑在 commonPool,工作窃取并行,受核数限制。阻塞会拖累 commonPool。

#
★★

62. ForkJoinPool 工作窃取(Work-Stealing)在多核 CPU 与 NUMA 架构下的性能特征

ForkJoinPool 工作窃取(Work-Stealing)在多核 CPU 与 NUMA 架构下的性能特征是什么?

  • 工作窃取
  • NUMA
  • 性能特征

工作窃取:空闲 worker 从其他 worker 的 deque 尾部窃取任务,实现负载均衡。多核 CPU:窃取减少空闲,提升并行利用率,但窃取有缓存一致性开销(跨核访问)。NUMA 架构:任务/数据有本地内存(NUMA 节点),窃取到其他节点的任务需访问远程内存,延迟高;工作窃取不考虑 NUMA 本地性,可能产生跨 NUMA 访问,性能下降。特征:多核下窃取均衡负载但 NUMA 下跨节点访问有代价。优化:NUMA 感知调度或减少任务跨节点。

工作窃取均衡负载,但 NUMA 下跨节点窃取有远程内存延迟。需考虑数据本地性。

#
★★

63. ForkJoinPool 的工作窃取(Work-Stealing)算法在并行流与并行任务中的性能特征

ForkJoinPool 的工作窃取(Work-Stealing)算法在并行流与并行任务中的性能特征是什么?

  • 工作窃取
  • 并行流
  • 性能特征

工作窃取算法:每个 worker 有本地双端队列,任务从队头取(LIFO),空闲 worker 从其他队列队尾窃取(FIFO),减少竞争。性能特征:1) 任务拆分粒度影响窃取效率(粒度太细窃取多、开销大;太粗不均);2) 大任务窃取可分片,小任务窃取开销显著;3) 并行流中,任务粒度由 spliterator 决定,粒度影响并行度;4) 窃取减少 idle 提升吞吐,但跨 worker 窃取有缓存与同步开销。特征:均匀、粒度适中的任务窃取性能最优。

工作窃取均衡负载,性能受任务粒度影响。粒度太粗不均、太细窃取开销大。

#
★★

64. ForkJoinTask 的 fork/join 与 RecursiveTask/RecursiveAction

ForkJoinTask 的 fork/join 与 RecursiveTask/RecursiveAction 是什么?

  • fork/join
  • RecursiveTask
  • RecursiveAction

ForkJoinTask 是任务抽象,fork() 异步提交子任务、join() 等待结果。RecursiveTask 继承 ForkJoinTask,用于有返回值的递归任务(compute 返回结果);RecursiveAction 用于无返回值的递归任务(compute 无返回)。典型用法:在 compute 中判断任务是否足够小,够小则直接计算,否则拆分成子任务 fork 并行,再 join 聚合。例如并行求和、归并排序。区分:RecursiveTask 有返回、RecursiveAction 无返回。

fork/join 并行拆分,RecursiveTask 有返回、RecursiveAction 无返回。递归并行计算的标准方式。

#
★★

65. ForkJoinTask 的 quietlyComplete/quietlyJoin

ForkJoinTask 的 quietlyComplete/quietlyJoin 是什么?

  • quietlyComplete
  • quietlyJoin
  • 语义

quietlyComplete():静默完成当前任务(不设置结果,无异常),后续 join 返回 null(不抛)。quietlyJoin():静默等待任务完成,不检查异常(普通 join 会抛异常/检查结果),也不返回结果。用途:当不关心结果或异常时,quietlyJoin 避免 join 的异常处理与结果获取开销;quietlyComplete 快速标记完成。适用场景:任务结果由副作用获取,或需忽略异常。注意:quietlyJoin 不抛异常,异常会被吞,需自行处理。

quietlyJoin 静默等待不抛异常,quietlyComplete 静默完成。适合不关心结果/异常的场景。

#
★★

66. 线程池中任务重试的编排,RetryTemplate 与池内线程的配合,重试次数与超时的总预算如何控制

线程池中任务重试的编排:RetryTemplate 与池内线程的配合,重试次数与超时的总预算如何控制?

  • 重试编排
  • RetryTemplate
  • 总预算

线程池任务重试:用 RetryTemplate(Spring Retry)在任务内编排重试(重试次数、退避、异常类型)。配合池内线程:重试在同一池线程内执行,占用线程;需控制总预算避免线程被重试长时间占用。总预算控制:重试次数 × 单次超时 = 总时间上限,超预算即放弃;重试间隔(退避)也计入。设计:设定重试次数上限与单次超时,总预算 = 次数×超时,超限快速失败;避免重试占用线程过久(线程池饥饿)。用独立重试线程池或超时兜底。

重试占用池线程,需控制"次数×超时"总预算,避免线程被重试拖住。

#
★★

67. 线程池任务的执行阶段划分(排队/执行/完成)如何埋点,阶段耗时对容量规划的意义

线程池任务的执行阶段划分(排队/执行/完成)如何埋点?阶段耗时对容量规划的意义是什么?

  • 阶段划分
  • 埋点
  • 容量规划

阶段划分:排队(提交到开始)、执行(开始到完成)、完成(结束)。埋点:任务包装器记录提交时间戳、开始时间戳、结束时间戳,计算排队/执行耗时。容量规划意义:1) 排队时间长 → 线程不足/队列堆积,需扩线程或调队列;2) 执行时间长 → 任务本身慢或资源不足,需优化任务或分布式;3) 完成时间分布 → 判断吞吐与延迟。阶段耗时区分"等待"与"执行",是容量规划的核心依据。

阶段埋点区分排队/执行耗时,排队长约线程,执行长约任务,指导容量规划。

#
★★

68. 线程池任务执行完后的资源清理,ThreadLocal.remove、连接归还与事务边界由谁保证

线程池任务执行完后的资源清理:ThreadLocal.remove、连接归还与事务边界由谁保证?

  • ThreadLocal.remove
  • 连接归还
  • 事务边界

线程池任务结束后的资源清理:1) ThreadLocal.remove:任务内必须清理(或包装器 finally),否则池线程复用泄漏;2) 连接归还:连接(DB/HTTP)通过 try-with-resources/finally 归还连接池;3) 事务边界:由事务管理器(Spring @Transactional)在任务内管理,提交/回滚由框架保证。为保证责任,用统一包装器(finally 清理 ThreadLocal、归还连接)+ 事务注解管理事务。核心是"任务边界内的资源必须显式释放"。

清理责任:ThreadLocal 任务内 remove、连接 finally 归还、事务由框架管。统一包装器保证。

#
★★

69. 线程池任务执行中的 ThreadMXBean 采样如何定位阻塞点,线程 dump 与池状态的关联分析

线程池任务执行中的 ThreadMXBean 采样如何定位阻塞点?线程 dump 与池状态的关联分析是什么?

  • ThreadMXBean 采样
  • 阻塞点
  • 关联分析

ThreadMXBean 采样:用 ThreadMXBean.getThreadInfo 周期性采样线程状态(BLOCKED/WAITING/RUNNABLE)与栈,识别阻塞点(等锁、等 IO、等条件)。关联分析:线程 dump 显示池线程状态,结合池状态(活跃线程数、队列长度)判断:池线程全 BLOCKED → 阻塞在业务资源;池线程 RUNNABLE 但队列长 → 任务慢/CPU 密集;池线程少 → 线程不足。把"线程栈阻塞点"与"池的活跃/队列"关联,定位是池容量问题还是任务阻塞问题。

ThreadMXBean 采样定位线程阻塞点,结合池活跃/队列判断是容量还是任务问题。

#
★★

70. 线程池任务提交速率与消费能力的匹配,背压信号(队列水位、拒绝计数)如何反馈给生产者

线程池任务提交速率与消费能力的匹配:背压信号(队列水位、拒绝计数)如何反馈给生产者?

  • 提交速率
  • 消费能力
  • 背压信号

提交速率 > 消费能力时,队列上涨、拒绝增加。背压信号反馈给生产者:1) 队列水位(size/capacity)作为信号,超阈值时生产者降速/暂停提交;2) 拒绝计数:拒绝发生时生产者降速或走降级;3) 用回调/状态暴露给生产者,生产者据此调整提交速率。实现:生产者轮询队列水位或通过监控触发降速;或提交时检查(tryAcquire 信号量)决定是否提交。背压让"提交速率"自适应"消费能力",避免积压。

队列水位/拒绝计数作为背压信号,生产者据此降速,实现生产-消费匹配。

#
★★

71. 线程池与 CompletableFuture 默认池混用时的资源竞争,为何建议显式指定 Executor

线程池与 CompletableFuture 默认池混用时的资源竞争:为何建议显式指定 Executor?

  • 默认池混用
  • 资源竞争
  • 显式 Executor

CompletableFuture 异步阶段默认用 commonPool,若与业务线程池混用,commonPool 被阻塞任务占满会拖累其他使用 commonPool 的任务(并行流、其他 CF)。建议显式指定 Executor:用 thenApplyAsync(executor) 显式指定业务线程池,隔离资源,避免 commonPool 竞争与串扰。理由:1) commonPool 是共享的,被占满影响全局;2) 显式 Executor 便于隔离、监控、调优;3) 避免阻塞任务污染 commonPool。最佳实践:CF 异步阶段显式指定专用 Executor。

commonPool 是全局共享,混用易被阻塞任务污染。显式指定 Executor 隔离资源。

#
★★

72. 线程池任务的优雅停机,应用收到 SIGTERM 后如何先停止接收任务再排空队列

线程池任务的优雅停机:应用收到 SIGTERM 后如何先停止接收任务再排空队列?

  • 优雅停机
  • SIGTERM
  • 排空队列

优雅停机流程:1) 收到 SIGTERM,先停止接收新任务(shutdown(),不再接受提交,但已提交任务继续);2) 等待已提交任务完成(awaitTermination(timeout) 排空队列);3) 超时未完成则 shutdownNow() 中断并返回未完成任务;4) 处理未完成任务(补偿/持久化)。关键:先 shutdown 停止接收,再 awaitTermination 排空,超时兜底 shutdownNow。避免立即 shutdownNow 丢失任务,也不无限等待。

优雅停机 = shutdown 停止接收 → awaitTermination 排空 → 超时 shutdownNow 兜底。

#
★★

73. 线程池任务执行异常时的指标与日志关联,错误率、重试次数与线程池状态如何统一观测

线程池任务执行异常时的指标与日志关联:错误率、重试次数与线程池状态如何统一观测?

  • 错误率
  • 重试次数
  • 统一观测

统一观测:任务失败时记录错误率指标(任务失败数/总数)、重试次数指标,并写日志(含 TraceId、任务信息、线程池名),同时关联线程池状态(活跃线程、队列、拒绝数)。通过指标 + 日志 + 池状态关联,定位失败是"任务本身异常"还是"池饱和/资源不足"。实现:任务包装器统一捕获异常、记录指标与日志、关联池指标(Micrometer)+ 日志上下文,形成统一可观测视图。

错误率+重试+池状态三者关联,区分任务异常与池容量问题。统一指标+日志追踪。

#
★★

74. 线程池任务顺序性与性能的平衡,单线程 Executor 保序与多线程并发的切换阈值

线程池任务顺序性与性能的平衡:单线程 Executor 保序与多线程并发的切换阈值是什么?

  • 顺序性
  • 单线程保序
  • 多线程并发

单线程 Executor(newSingleThreadExecutor)保证任务严格按提交顺序执行,但吞吐受限(串行)。多线程并发吞吐高但顺序不定。切换阈值:当任务量大、顺序性要求可放宽(如任务可并行但需按批次保序)时,用多线程;当必须严格保序(如顺序处理日志、状态机)时用单线程。可结合:批次保序(一批内并行、批次间串行)或用分区(同 key 单线程、不同 key 并行)。平衡:在"保序需求"与"吞吐需求"间权衡,按需选择单线程或分区。

单线程保序降吞吐,多线程提吞吐失序。保序需求决定用单线程还是分区/并发。

#
★★

75. 线程池任务的优先级与超时组合,延迟队列实现定时任务的精度与线程数关系

线程池任务的优先级与超时组合:延迟队列实现定时任务的精度与线程数关系是什么?

  • 延迟队列
  • 定时精度
  • 线程数

用 DelayQueue/ScheduledThreadPoolExecutor 实现定时任务:任务按到期时间排序,线程数影响精度——线程数少时,到期任务可能排队等线程,触发延迟;线程数多时,多个任务可同时触发,精度更高。但线程数过多增加切换开销。精度与线程数关系:定时精度受"线程数 + 任务量"影响,线程充足则到期即执行,线程不足则排队延迟。ScheduledThreadPoolExecutor 默认单线程,周期任务可能互相影响,需按任务量调线程数。权衡:够用的线程数保证准时,避免过度。

定时精度依赖线程是否充足,线程不足到期任务排队延迟。按任务量调线程数。

#
★★

76. 线程池在虚拟线程与平台线程混部时的线程数规划,IO 密集任务迁移后剩余 CPU 密集任务的池化策略

线程池在虚拟线程与平台线程混部时的线程数规划:IO 密集任务迁移后剩余 CPU 密集任务的池化策略是什么?

  • 混部
  • IO 密集迁移
  • CPU 密集池化

虚拟线程与平台线程混部:IO 密集任务迁移到虚拟线程(免池化、阻塞卸载),剩余 CPU 密集任务保留平台线程池。池化策略:CPU 密集任务用平台线程池,线程数 ≈ 核数(受核数限制);虚拟线程不占池(直接执行 IO 任务)。规划:1) 识别 IO 密集与 CPU 密集任务,分离;2) IO 密集用虚拟线程,CPU 密集用平台池(核数);3) 平台池线程数不因 IO 任务迁移而减少(IO 已不在池内);4) 监控确保平台池只跑 CPU 密集,避免混合。关键:任务按类型分流,池化只针对 CPU 密集。

混部时 IO 密集走虚拟线程,CPU 密集留平台池(核数)。按类型分流,池化只针对 CPU 密集。

#
★★

77. LinkedTransferQueue 的 transfer 与 tryTransfer

LinkedTransferQueue 的 transfer 与 tryTransfer 是什么?

  • transfer
  • tryTransfer
  • 语义

LinkedTransferQueue.transfer(e):把元素交给等待的消费者,若没有消费者则阻塞直到消费者出现(无缓冲交接)。tryTransfer(e):尝试立即交给消费者,无消费者则返回 false(不阻塞)。tryTransfer(e, timeout):限时等待。与 put 的区别:put 总是入队(无界不阻塞),transfer 要求"立即交接"(无消费者则阻塞)。语义:transfer 是"生产者-消费者直接交接",tryTransfer 非阻塞尝试。用于需要"确保消费者接住"的场景。

transfer 阻塞直到消费者接住,tryTransfer 非阻塞尝试。是"交接"语义的队列。

#
★★

78. ManagedBlocker 与 ForkJoinPool 阻塞协作

ManagedBlocker 与 ForkJoinPool 阻塞协作是什么?

  • ManagedBlocker
  • ForkJoinPool
  • 阻塞协作

ForkJoinPool 中,若任务阻塞(如 IO、锁),会占用池线程,可能耗尽线程导致饥饿。ManagedBlocker 是解决机制:实现 ManagedBlocker 接口(block() 返回是否完成、isReleasable() 判断是否可释放),在阻塞时调用 ForkJoinPool.managedBlock(blocker),ForkJoinPool 会临时增加/补充线程保证池内仍有线程执行其他任务,避免阻塞任务占满池。协作:阻塞任务通过 ManagedBlocker 告知池"我要阻塞",池提供额外线程,防止饥饿。适用于池内阻塞任务场景。

ManagedBlocker 让 FJP 在阻塞时补线程,防止阻塞任务占满池导致饥饿。

#
★★

79. RecursiveTask 的合并阈值(threshold)调优

RecursiveTask 的合并阈值(threshold)调优是什么?

  • 合并阈值
  • 任务拆分
  • 调优

RecursiveTask 在 compute 中按阈值判断是否继续拆分:任务规模 > 阈值则拆分,否则直接计算。阈值调优:1) 阈值太大 → 任务拆分少、并行度低、负载不均;2) 阈值太小 → 拆分过多、任务数量大、窃取与调度开销大(任务碎片化)。最佳阈值:使任务粒度适中(几百到几千元素),平衡并行度与调度开销。调优方法:压测不同阈值观察吞吐与延迟,选择性能拐点。阈值与数据规模、核数相关,需实测。

阈值决定任务粒度,太大并行度低、太小开销大。压测选最佳阈值。

#
★★

80. ScheduledExecutorService.scheduleAtFixedRate 与 scheduleWithFixedDelay

ScheduledExecutorService.scheduleAtFixedRate 与 scheduleWithFixedDelay 的区别是什么?

  • scheduleAtFixedRate
  • scheduleWithFixedDelay
  • 区别

scheduleAtFixedRate(period):固定周期,任务按固定间隔触发(上一次开始 + period),若任务执行超过 period,则周期延后(不重叠,但节奏按固定速率)。scheduleWithFixedDelay(delay):固定延迟,任务完成后延迟 delay 再执行下一次(间隔 = 执行时间 + delay)。区别:AtFixedRate 按"固定开始间隔",FixedDelay 按"完成后再等 delay"。AtFixedRate 适合节奏固定、任务通常短于周期;FixedDelay 适合任务需完全串行、避免重叠。

AtFixedRate 固定开始间隔,FixedDelay 完成后间隔。任务超时应预估,避免重叠。

#
★★

81. 线程池任务的批量结果收集,invokeAll 与 Stream 并行在异常处理与超时上的差异

线程池任务的批量结果收集:invokeAll 与 Stream 并行在异常处理与超时上的差异是什么?

  • invokeAll
  • Stream 并行
  • 异常/超时差异

invokeAll:提交所有任务,等待全部完成,返回 Future 列表,异常在 get 时抛 ExecutionException,支持超时(invokeAll(tasks, timeout))。Stream 并行:parallelStream 用 commonPool 并行处理,异常直接抛出(处理中),无统一超时控制(需自行或用 CompletableFuture)。差异:invokeAll 可统一超时、异常封装在 Future;Stream 并行无内建超时、异常直接抛、用 commonPool。批量收集:invokeAll 适合需要超时与异常控制的场景,Stream 并行适合简单并行处理。

invokeAll 支持超时与异常封装,Stream 并行无超时、异常直接抛。按需选择。

#
★★

82. StructuredTaskScope.fork 的资源继承与异常传播

StructuredTaskScope.fork 的资源继承与异常传播是什么?

  • fork 资源继承
  • 异常传播
  • StructuredTaskScope

StructuredTaskScope.fork(subtask) 创建子任务,子任务继承作用域的上下文(Scoped Values 等),但子任务在独立线程/虚拟线程执行。资源继承:通过 Scoped Values 传递不可变上下文(不随线程,随作用域);代码结构(方法)继承。异常传播:子任务异常被捕获,通过策略(ShutdownOnFailure)聚合,join 时抛整体异常;子任务异常不直接打断父,而是经 scope 传播。父在 join 后按策略处理子任务异常。

fork 子任务继承作用域上下文(Scoped Values),异常经策略聚合传播。

#
★★

83. Thread.onSpinWait() 的硬件提示

Thread.onSpinWait() 的硬件提示是什么?

  • onSpinWait
  • 硬件提示
  • 用途

Thread.onSpinWait()(JDK 9)向处理器提示"当前线程正在自旋等待",处理器可据此优化(如 x86 的 PAUSE 指令、降低功耗、减少流水线与缓存竞争影响)。它是自旋等待的"软提示",用于"预期很快获得资源"的短期自旋。硬件提示让 JVM 把自旋映射为低功耗的等待指令,减少对同核其他线程的干扰。适用于自旋锁/短等待场景,不用于阻塞或长等待。

onSpinWait 是给处理器的自旋提示,降低功耗与干扰,适合短自旋。

#
★★

84. ThreadPoolExecutor 的四种拒绝策略及适用场景

ThreadPoolExecutor 的四种拒绝策略及适用场景是什么?

  • 四种策略
  • 适用场景
  • 各策略的触发机制与默认策略(AbortPolicy 抛异常快速失败)

四种拒绝策略:1) AbortPolicy:抛 RejectedExecutionException(默认),适合"快速失败、由调用方处理";2) CallerRunsPolicy:由提交线程执行任务,背压调用方,适合"可接受调用方参与、需削峰";3) DiscardPolicy:静默丢弃,适合"可丢弃、非关键";4) DiscardOldestPolicy:丢弃最旧(队首)任务再重试,适合"新任务优先"(如刷最新数据)。选择:关键任务用 Abort 快速失败,可降级用 CallerRuns,可丢弃用 Discard。

四种策略按"失败/背压/丢弃/丢最旧"选择。默认 Abort 快速失败。

#
★★

85. ThreadPoolExecutor 的扩展点(beforeExecute/afterExecute)

ThreadPoolExecutor 的扩展点(beforeExecute/afterExecute)是什么?

  • beforeExecute
  • afterExecute
  • 扩展

ThreadPoolExecutor 提供钩子:beforeExecute(Thread, Runnable) 在任务执行前调用,afterExecute(Runnable, Throwable) 在任务执行后调用(含异常)。用途:埋点(任务耗时、指标)、日志、上下文设置(beforeExecute 设置 MDC、afterExecute 清理)、异常记录、彻底执行 guarantee。覆盖 execute 时需注意 beforeExecute 只对 execute 的任务生效(submit 的 FutureTask 也经过)。扩展点用于任务级监控与增强,无需改任务本身。

beforeExecute/afterExecute 是任务级扩展点,用于埋点、日志、上下文清理。

#
★★

86. 线程池饥饿的典型特征(任务等待时间增长、CPU 利用率低)与排查步骤

线程池饥饿的典型特征(任务等待时间增长、CPU 利用率低)与排查步骤是什么?

  • 饥饿特征
  • CPU 利用率低
  • 排查步骤

线程池饥饿特征:任务等待时间长、队列增长、但 CPU 利用率低(线程被阻塞/等待而非计算)。排查:1) 观察队列长度与等待时间(增长);2) 线程 dump 看池线程状态(是否 BLOCKED/WAITING 阻塞而非执行);3) 分析阻塞点(锁、IO、下游);4) 检查是否有慢任务/阻塞任务占满线程;5) 检查是否嵌套提交(同池子任务饿死)。核心:CPU 低 + 等待长 = 线程被阻塞占满,定位阻塞资源。

饥饿 = 等待长 + CPU 低,线程阻塞而非计算。dump 定位阻塞点。

#
★★

87. 线程池任务的超时中断与 CompletableFuture.orTimeout 的协作,超时后任务仍在执行如何治理

线程池任务的超时中断与 CompletableFuture.orTimeout 的协作,超时后任务仍在执行如何治理?

  • 超时中断
  • orTimeout
  • 治理

orTimeout 只改变 CF 完成状态(超时),不取消底层任务。协作:orTimeout 超时后,需配合 cancel(true) 中断任务,或任务自身检查超时退出。治理"超时后任务仍在执行":1) orTimeout 后调用 cancel(true)(中断,但任务需响应中断);2) 任务内检查超时/中断标志主动退出;3) 用虚拟线程或独立线程池执行超时敏感性任务;4) 监控并强制终止(不推荐)。核心:超时不能只靠 CF 完成状态,需任务协作中断才能真正停止。

orTimeout 只标记超时,真正停止需任务响应中断/cancel。协作式治理。

#
★★

88. allowCoreThreadTimeOut 在突发流量与空闲回收场景下的吞吐抖动与稳定性权衡

allowCoreThreadTimeOut 在突发流量与空闲回收场景下的吞吐抖动与稳定性权衡是什么?

  • allowCoreThreadTimeOut
  • 吞吐抖动
  • 稳定性

allowCoreThreadTimeOut(true) 让核心线程空闲回收,突发流量时:核心线程被回收,突发到来需重新创建线程(有创建延迟),首任务延迟增大(吞吐抖动)、P99 波动。稳定性权衡:空闲回收省资源但突发时重建线程引入抖动;保持核心线程(false)稳定但空闲占资源。取舍:对延迟敏感、流量波动大的业务,保持核心线程(false)保稳定;对流量平稳、资源紧张的场景,开启回收省资源。根据业务延迟敏感度选择。

allowCoreThreadTimeOut 省资源但突发时重建线程抖动。稳定优先用 false,资源优先 true。

#
★★

89. orTimeout/completeOnTimeout(JDK 9+)在异步任务超时控制上的工程价值

orTimeout/completeOnTimeout(JDK 9+)在异步任务超时控制上的工程价值是什么?

  • orTimeout
  • completeOnTimeout
  • 超时控制

orTimeout(duration):超时后以 TimeoutException 完成 CF,用于超时兜底(调用方 get 时不再无限等待)。completeOnTimeout(value, duration):超时后以默认值完成,用于降级(超时返回默认值继续)。工程价值:1) 声明式设置超时,避免手动 get(timeout) 的复杂;2) 超时降级(默认值)保持流程继续;3) 与链式组合(整个 CF 链超时)。注意:两者只改 CF 完成,不取消底层任务(需配合 cancel)。用于 RPC 超时、异步调用兜底。

orTimeout 超时抛异常、completeOnTimeout 超时降级默认值,声明式超时控制。不取消底层任务。

#
★★

90. shutdown 与 shutdownNow 的差异如何在批处理与交互式服务中分别选择

shutdown 与 shutdownNow 的差异如何在批处理与交互式服务中分别选择?

  • shutdown
  • shutdownNow
  • 场景选择

shutdown():停止接收新任务,已提交任务继续执行(优雅停机,等队列排空)。shutdownNow():立即停止,中断正在执行的线程,返回未执行任务(不等待)。选择:批处理(任务可等完成、不允许丢失)用 shutdown + awaitTermination,排空后关闭;交互式服务(需快速响应、可接受中断)用 shutdownNow 快速停止,返回未完成任务补偿。差异核心:shutdown 优雅等完成,shutdownNow 立即中断。按"是否允许任务完成/丢失"选择。

shutdown 优雅排空,shutdownNow 立即中断。批处理用 shutdown,交互式快速响应用 shutdownNow。

#
★★

91. thenAccept/thenRun/thenApply 的消费差异

thenAccept/thenRun/thenApply 的消费差异是什么?

  • thenAccept
  • thenRun
  • thenApply

thenApply(fn):消费上一步结果并返回新结果(映射,返回 CF )。thenAccept(fn):消费上一步结果但不返回(Consumer,返回 CF )。thenRun(fn):不消费结果(参数为空),只执行一段 Runnable,返回 CF。差异:thenApply 有返回、thenAccept 消费结果无返回、thenRun 不接收结果。按"是否需要结果与返回值"选择:需转换结果用 thenApply,只需消费结果用 thenAccept,无需结果用 thenRun。

thenApply 映射返回、thenAccept 消费无返回、thenRun 不接收结果。三者的消费粒度不同。

#
★★

92. thenCompose 与 thenCombine 的执行顺序

thenCompose 与 thenCombine 的执行顺序是什么?

  • thenCompose
  • thenCombine
  • 执行顺序

thenCompose(fn):串行依赖,上一步完成后才执行 fn(fn 返回 CF),fn 依赖上一步结果,是"顺序执行"(B 依赖 A)。thenCombine(other, fn):并行,两个 CF 都完成后才执行 fn 组合(fn 接收两个结果),是"并发执行"(A 与 B 同时进行,都完成后组合)。执行顺序:thenCompose 是 A→B 串行;thenCombine 是 A∥B 并行后组合。选型:任务有依赖用 thenCompose,独立任务并行组合用 thenCombine。

thenCompose 串行依赖,thenCombine 并行组合。按任务是否独立选择。

#
★★

93. whenComplete vs handle 的差异(异常是否改变结果)

whenComplete vs handle 的差异(异常是否改变结果)是什么?

  • whenComplete
  • handle
  • 异常语义

handle(fn):无论成败都执行 fn(result, ex),fn 返回新值,可以改变结果(异常时返回替代值,异常被吞掉)。whenComplete(fn):无论成败都执行 fn(result, ex),fn 返回 void,不改变结果(原结果/异常继续传播)。差异:handle 可转换结果(能吞异常),whenComplete 只观察不改变(异常继续传播)。需要"异常时返回替代值"用 handle,需要"无论成败做钩子但保持结果"用 whenComplete。

handle 能转换结果(处理异常),whenComplete 只观察不改变(异常传播)。按是否需改结果选。

#
★★

94. 任务提交是否会直接进入 maxPoolSize 路径,存在哪些隐藏延迟

任务提交是否会直接进入 maxPoolSize 路径?存在哪些隐藏延迟?

  • 提交路径
  • maxPoolSize
  • 隐藏延迟

任务提交不会直接进 maxPoolSize:判定顺序是"核心线程 → 队列 → maxPoolSize → 拒绝"。只有核心线程满且队列满才创建非核心线程(到 maxPoolSize)。隐藏延迟:1) 队列操作(offer/poll)有锁/CAS 开销;2) 创建新线程(非核心)有线程创建开销(栈分配、原生线程);3) 首次提交时创建核心线程有初始化延迟;4) 队列满时扩容判断有竞争。maxPoolSize 路径需先"队列满"才触发,不会跳过队列直接扩线程。理解提交路径避免误判。

提交路径先核心后队列,队列满才扩到 maxPoolSize。线程创建与队列竞争是隐藏延迟。

#
★★

95. 虚拟线程与平台线程池的共存,IO 密集型任务何时必须用虚拟线程?

虚拟线程与平台线程池的共存:IO 密集型任务何时必须用虚拟线程?

  • 虚拟线程
  • 平台线程池
  • IO 密集

IO 密集型任务必须用虚拟线程的场景:1) 大量并发阻塞 IO(网络、DB、文件),平台线程池线程数受限(核数),阻塞会占满线程导致并发不足;2) 需要高并发(万级)阻塞请求,平台线程无法承载;3) 阻塞 I/O 占主导,用虚拟线程卸载载体线程,无需池化。平台线程池仍用于:CPU 密集、需限流/隔离、需精确控制并发的场景。必须用虚拟线程的判断:IO 极其密集 + 并发需求高 + 阻塞占主导。虚拟线程与平台线程池共存,按任务类型分流。

大量并发阻塞 IO 且需高并发时必用虚拟线程,平台线程池受核数限制无法承载。

#
★★

96. 线程池任务在高峰期触发的类加载/初始化对首字节延迟的影响,如何预热关键路径

线程池任务在高峰期触发的类加载/初始化对首字节延迟的影响,如何预热关键路径?

  • 类加载/初始化
  • 首字节延迟
  • 预热

高峰期新任务首次触发类加载/JIT 编译/连接池初始化,会引入额外延迟(首字节延迟高)。预热:1) 启动时预热关键路径(提前调用触发类加载与 JIT);2) 预热连接池/缓存/线程池(prestart 核心线程);3) 用 JMH 或启动预热任务执行关键代码,触发 JIT 编译;4) 上线前压测预热。预热减少"首次请求"的类加载/初始化延迟,稳定首字节。关键路径(首个请求)预热可显著降低 P99。

峰值首字节延迟高源于类加载/JIT/初始化。预热关键路径(预加载、预编译)可缓解。

#
★★

97. 线程池的线程数选择在微服务网关场景的实践,与下游连接池容量、超时时间如何联合规划

线程池的线程数选择在微服务网关场景的实践:与下游连接池容量、超时时间如何联合规划?

  • 网关线程数
  • 下游连接池
  • 超时联合规划

网关线程池线程数规划需与下游连接池、超时联合:1) 线程数应匹配下游连接池容量(并发请求 ≤ 连接数,否则线程等连接);2) 超时时间(连接/读超时)决定线程被占用的最长时间,超时越短线程释放越快;3) 线程数 = 并发请求上限,受连接池容量与下游吞吐约束;4) 用信号量/隔离控制对下游的并发,避免连接池被打爆。联合规划:让线程数 ≤ 连接池容量,超时兜底释放线程,监控连接与线程利用率。

网关线程数受下游连接池容量与超时约束,联合规划避免线程等连接。

#
★★

98. JDK 25 虚拟线程调度器与平台线程调度器在 I/O 密集场景下的上下文切换开销与吞吐差异

JDK 25 虚拟线程调度器与平台线程调度器在 I/O 密集场景下的上下文切换开销与吞吐差异是什么?

  • 虚拟线程调度器
  • 平台线程调度器
  • 上下文切换/吞吐

I/O 密集场景:平台线程阻塞时上下文切换(OS 级)开销大,且线程数受限(核数),阻塞占线程导致并发不足。虚拟线程调度器(ForkJoinPool 载体)在虚拟线程阻塞时卸载(continuation 挂起,非 OS 线程切换),载体线程被复用,上下文切换开销小(用户态切换),能承载大量并发阻塞。吞吐:虚拟线程在 I/O 密集下吞吐远高于平台线程(无阻塞占线程、无频繁 OS 切换)。差异:虚拟线程卸载是轻量调度,平台线程阻塞是重 OS 切换。

虚拟线程阻塞卸载(轻量)vs 平台线程阻塞(OS 切换),I/O 密集下虚拟线程吞吐高。

#
★★

99. 线程池任务的资源隔离,与信号量/舱壁模式组合时的参数预算如何分配

线程池任务的资源隔离:与信号量/舱壁模式组合时的参数预算如何分配?

  • 资源隔离
  • 信号量/舱壁
  • 参数预算

线程池 + 信号量/舱壁组合隔离:线程池控制执行并发,信号量/舱壁限制对下游的并发(如每个下游一个舱壁)。参数预算分配:1) 线程池线程数 = 总并发预算;2) 信号量许可 = 对某下游的并发上限(≤ 下游连接池);3) 舱壁(每个依赖独立信号量/线程池)隔离故障。分配原则:总预算 = 线程数,细分到各依赖的信号量许可,保证每层不超限。避免线程数 > 信号量(线程等许可)或信号量 > 连接池(连接不足)。按依赖容量分配。

线程池预算 + 信号量/舱壁细分,各层不超限,避免线程等许可或连接不足。

#

100. JDK 25 中虚拟线程池与 StructuredTaskScope(JEP 505,5th Preview)的协作与边界

JDK 25 中虚拟线程池与 StructuredTaskScope(JEP 505,5th Preview)的协作与边界是什么?

  • 虚拟线程池
  • StructuredTaskScope
  • 协作/边界

虚拟线程池(newVirtualThreadPerTaskExecutor)提供轻量执行,StructuredTaskScope 提供结构化编排。协作:StructuredTaskScope 内 fork 的子任务在虚拟线程上运行(Scope 默认用虚拟线程执行任务),获得"结构化 + 高并发"。边界:1) 虚拟线程池是"执行器",只管任务的并发执行;StructuredTaskScope 是"编排器",管生命周期/异常/取消;2) 结构化并发强调作用域内任务有界、可取消,虚拟线程池强调无界并发;3) 超出作用域(fire-and-forget)用虚拟线程池,需要结构化生命周期用 SScope。协作:SScope 内置虚拟线程执行,二者互补。

虚拟线程池提供执行,SScope 提供编排,SScope 内置虚拟线程执行,互补不冲突。

#

101. 反应式编程与异步回调的核心差异

反应式编程与异步回调的核心差异是什么?

  • 反应式编程
  • 异步回调
  • 差异

反应式编程(Reactive,如 Reactor/RxJava):基于数据流与背压,声明式组合异步操作,支持流式处理、背压控制、错误传播,代码声明式但学习曲线陡。异步回调:基于回调函数(回调地狱),隐式线程切换,无背压,错误处理繁琐。核心差异:1) 反应式是"数据流+背压",回调是"单次回调";2) 反应式声明式组合,回调命令式嵌套;3) 反应式有背压(控制生产速率),回调无;4) 反应式错误传播链式,回调需手动处理。反应式更强大但复杂,回调简单但难扩展。

反应式是"流+背压+声明式",回调是"单次+嵌套"。高吞吐流式用反应式,简单异步用回调。

#

102. 如何通过 JMX/Micrometer 暴露指标并接入告警,使容量问题在事故前被定位

如何通过 JMX/Micrometer 暴露指标并接入告警,使容量问题在事故前被定位?

  • JMX/Micrometer
  • 指标暴露
  • 告警

用 Micrometer 暴露线程池/连接池/队列等指标(Gauge、Counter),注册到 Prometheus/Graphite 等 registry;JMX 经 JmxReporter 暴露 ThreadPoolExecutor 的 MXBean 指标。告警:设置阈值(线程池活跃率、队列水位、拒绝数、连接池利用率),超过即告警,配合监控大盘。容量问题在事故前被定位:监控指标趋势(队列增长、活跃率上升),提前预警容量不足,避免事故。核心是"指标暴露 + 阈值告警 + 趋势监控"。

Micrometer/JMX 暴露指标,阈值告警提前预警容量,监控趋势防事故。

#

103. 对于 ThreadPoolExecutor 的 RejectedExecutionHandler,AbortPolicy 默认抛 RejectedExecutionException 在异步消息消费链中如何避免消费阻塞

对于 ThreadPoolExecutor 的 RejectedExecutionHandler,AbortPolicy 默认抛 RejectedExecutionException 在异步消息消费链中如何避免消费阻塞?

  • AbortPolicy
  • 消息消费链
  • 避免阻塞

AbortPolicy 默认抛 RejectedExecutionException,在异步消息消费链中,若消费线程池满,消息处理抛异常。避免消费阻塞:1) 捕获 RejectedExecutionException,消息进入重试/死信队列(DLQ),不阻塞消费;2) 用消息重投(retry)机制,被拒消息重发;3) 用 CallerRunsPolicy 或阻塞重试让消费端背压;4) 监控拒绝并告警。核心:异常捕获后消息落入重试/死信,避免消费线程被阻塞或消息丢失。

AbortPolicy 抛异常,捕获后消息进重试/死信队列,避免消费阻塞与消息丢失。

#

104. 线程池任务执行中的线程切换成本如何度量,上下文切换与任务粒度的关系

线程池任务执行中的线程切换成本如何度量?上下文切换与任务粒度的关系是什么?

  • 线程切换成本
  • 上下文切换
  • 任务粒度

线程切换成本度量:用 /proc/pid/status 的 voluntary_ctxt_switches/nonvoluntary_ctxt_switches、perf stat context-switches、JFR 的线程切换事件,或用 OS 工具(pidstat)。上下文切换与任务粒度关系:任务粒度太细(任务多、执行时间短),切换次数多,切换开销占比大,吞吐下降;粒度太粗,并行度低、负载不均。理想任务粒度是"执行时间 >> 切换成本",使切换开销摊销。度量和权衡:监控切换率,调任务粒度使切换开销可接受。

切换成本用 OS 计数器度量。任务粒度要"执行时间 >> 切换成本",避免切换占比过高。

#

105. 线程池任务的执行顺序保证,FIFO 队列在单线程 worker 下是否天然有序,多线程如何保证提交顺序

线程池任务的执行顺序保证:FIFO 队列在单线程 worker 下是否天然有序?多线程如何保证提交顺序?

  • FIFO 队列
  • 单线程 worker
  • 提交顺序

FIFO 队列在单线程 worker 下天然有序:任务按提交顺序出队执行,单线程确保严格顺序。多线程下:FIFO 保证出队顺序,但多线程并发执行完成顺序不定(不保证完成顺序)。多线程保证提交顺序:1) 用分区(同一 key 提交到同一单线程 worker);2) 用序号/批次(按批次保序);3) 用单线程池保序(牺牲并发)。核心:单线程保序天然,多线程需分区或专用线程保序。

单线程 FIFO 天然有序,多线程需分区/单线程保序。按顺序需求选择。

#

106. JDK 25 中 LinkedTransferQueue 的 transfer 与 put/add 在生产者阻塞语义上的差异

JDK 25 中 LinkedTransferQueue 的 transfer 与 put/add 在生产者阻塞语义上的差异是什么?

  • transfer
  • put/add
  • 阻塞语义

LinkedTransferQueue.transfer(e):无消费者时阻塞直到消费者接住(交接语义)。put(e)/add(e):无界队列,总是入队成功,不阻塞(无容量限制)。差异:transfer 阻塞等待"消费者接住",put/add 不阻塞(无界直接入队)。JDK 25 中语义不变。transfer 用于"生产者必须确保消费者接住"的场景(如任务交接),put/add 用于"入队即可,不关心即时消费"。

transfer 阻塞直到消费者接住,put/add 无界不阻塞。交接语义 vs 普通入队。

#

107. 线程池任务执行时间过长(>1s)的识别与拆分策略,长任务与短任务混池的影响

线程池任务执行时间过长(>1s)的识别与拆分策略?长任务与短任务混池的影响?

  • 长任务识别
  • 拆分策略
  • 混池影响

长任务识别:任务包装器记录耗时,超阈值(如 1s)告警/日志;用 JFR/采样定位长任务。拆分策略:把长任务拆成多个短任务(分片、异步化、分阶段),提高并行与响应。长任务与短任务混池影响:长任务占线程久,短任务排队被拖慢(队头阻塞),P99 恶化;长任务占满池导致短任务饥饿。应对:分离长/短任务到不同池,或限制长任务并发。混池会互相拖累。

长任务占线程拖累短任务,应拆分长任务或分离池。混池互相影响。

#

108. 线程池任务在执行期间被中断的响应规范,InterruptedException 的传播与中断状态恢复

线程池任务在执行期间被中断的响应规范:InterruptedException 的传播与中断状态恢复是什么?

  • 中断响应
  • InterruptedException 传播
  • 中断状态恢复

任务执行期间被中断(shutdownNow/cancel 触发 interrupt),任务应:1) 捕获 InterruptedException 后恢复中断标志(Thread.currentThread().interrupt())或重新抛出,让上层感知;2) 检查 isInterrupted() 及时退出;3) 清理资源后停止。规范:不吞掉中断,恢复标志并传播。线程池中,任务内中断状态应在任务结束时清除(否则污染池线程的下一个任务)。响应规范:捕获→恢复标志→清理→退出。

中断响应要恢复标志、传播、清理。任务内吞中断会污染池线程。

#

109. 线程池任务的失败重试与消息投递的配合,任务执行失败后如何进入重试队列而非直接丢弃

线程池任务的失败重试与消息投递的配合:任务执行失败后如何进入重试队列而非直接丢弃?

  • 失败重试
  • 消息投递
  • 重试队列

任务失败(异常)后,不直接丢弃:1) 捕获异常,判断是否可重试(瞬时错误重试,永久错误丢弃);2) 把失败任务投递到重试队列(MQ 重试队列/延迟队列),延迟后重投;3) 控制重试次数,超限落死信队列(DLQ)人工处理;4) 记录失败指标与日志。配合消息投递:消息消费失败放入重试队列,按退避重试,避免消息丢失。核心是"失败→重试队列→限次→死信"。

失败任务进重试队列限次重试,超限落死信,避免直接丢弃。

#

110. 线程池任务与数据库连接池的容量匹配,池内线程等待连接时的行为与超时配置

线程池任务与数据库连接池的容量匹配:池内线程等待连接时的行为与超时配置是什么?

  • 连接池容量
  • 线程等连接
  • 超时配置

线程池任务与 DB 连接池匹配:若线程数 > 连接池容量,线程在等连接(阻塞),占线程不执行。行为:线程调用 getConnection() 阻塞等待直到连接可用或超时。超时配置:连接池获取超时(connectionTimeout,如 HikariCP 默认 30s),超时抛异常;线程获链接释要快。匹配:线程数 ≤ 连接池容量(或按比例),避免线程全等连接;监控连接池利用率与线程等待。核心是"线程数匹配连接池容量 + 连接获取超时兜底"。

线程数 > 连接池容量时线程等连接,需匹配容量并设获取超时兜底。

#

111. 线程池任务的幂等执行,任务重复执行如何通过业务幂等键去重,池内重试的边界

线程池任务的幂等执行:任务重复执行如何通过业务幂等键去重?池内重试的边界是什么?

  • 幂等执行
  • 幂等键去重
  • 重试边界

任务幂等执行:任务可能重试(重复执行),需通过业务幂等键去重:1) 任务带唯一幂等键(如订单号、请求 ID);2) 执行前检查幂等键是否已处理(数据库唯一索引/Redis SETNX);3) 已处理则跳过,未处理则执行并标记。池内重试边界:重试限制在"瞬时错误"(网络抖动),幂等错误(业务已处理)不重试;重试次数与超时有上限,避免无限重试。核心是"幂等键去重 + 重试分类(仅瞬时错误)"。

幂等键去重防重复执行,重试仅限瞬时错误且有限次。幂等是分布式任务的基础。

#

112. JEP 505 Structured Concurrency(5th Preview,JDK 25)

JEP 505 Structured Concurrency(5th Preview,JDK 25)是什么?

  • JEP 505
  • 结构化并发
  • 预览

JEP 505(JDK 25 第 5 次预览)是结构化并发(Structured Concurrency):用 StructuredTaskScope 以"作用域"组织并发任务,保证任务生命周期与代码结构一致。核心:try-with-resources 作用域、fork 子任务、join 等待、策略(ShutdownOnFailure/ShutdownOnSuccess)短路、异常聚合、级联取消、Scoped Values 传递。目标:消除"任务泄漏"(无主线程)、简化错误处理、提升可诊断性。第 5 次预览持续完善 API,是 Future 的现代替代。

JEP 505 用作用域+策略管理并发,结构化生命周期、异常聚合、级联取消。预览中。

#

113. JEP 505 与 JEP 506 Scoped Values 的协作模式

JEP 505 与 JEP 506 Scoped Values 的协作模式是什么?

  • JEP 505
  • JEP 506
  • 协作

JEP 505(结构化并发)与 JEP 506(Scoped Values)协作:StructuredTaskScope 内 fork 的子任务继承父作用域的 Scoped Values(不可变上下文),Scoped Values 不随线程而随作用域传递,天然适配结构化并发。协作模式:父任务用 Scoped Values 绑定请求上下文(TraceId、用户),fork 子任务自动继承,无需手动传递;子任务结束作用域释放。相比 ThreadLocal,Scoped Values 与结构化并发的生命周期一致,避免泄漏。

JEP 505 编排 + JEP 506 传递上下文,Scoped Values 随作用域传递,与结构化并发契合。

#

114. 线程池任务执行期间 JVM 线程 dump 的时机选择,采样多次 dump 与一次 dump 的定位差异

线程池任务执行期间 JVM 线程 dump 的时机选择?采样多次 dump 与一次 dump 的定位差异是什么?

  • dump 时机
  • 多次采样
  • 定位差异

线程 dump 时机:在问题持续时(如任务卡住、线程满)采样,而非随机。定位差异:一次 dump 只能看到某一瞬间的快照(可能漏掉瞬时状态、误判);多次 dump(间隔采样)能看出"一致性":1) 同一线程多次都卡在同一栈 → 确认阻塞点;2) 线程状态变化 → 动态问题;3) 对比多次 dump 找稳定瓶颈。多次采样更能定位"持久"问题(死锁、锁竞争、线程饥饿),单次用于快速初查。核心:多 dump 确认稳定状态。

多次 dump 确认稳定性,单次 dump 是瞬时快照。持久问题用多次采样。

#

115. JEP 525(结构化并发 6th Preview,JDK 26)

JEP 525(结构化并发 6th Preview,JDK 26)是什么?

  • JEP 525
  • 结构化并发
  • JDK 26

JEP 525(JDK 26,结构化并发第 6 次预览)继续演进 StructuredTaskScope:在 JEP 505 基础上完善 API(如 Subtask 的 result 处理、异常处理、作用域嵌套),可能引入新方法或调整语义,朝"正式化"推进。核心目标不变:结构化生命周期、任务泄漏防止、异常聚合、级联取消。第 6 次预览通常微调 API 并收集反馈,为最终稳定化做准备。结构化并发是 JDK 未来并发编程的重要方向。

JEP 525 是结构化并发的第 6 次预览,沿用 JEP 505 的 API 并继续完善,为正式化铺垫。

#

116. 线程池任务的延迟敏感度分级,不同 SLA 任务共用线程池时的抢占与优先级问题

线程池任务的延迟敏感度分级:不同 SLA 任务共用线程池时的抢占与优先级问题是什么?

  • 延迟敏感度
  • SLA 分级
  • 抢占/优先级

不同 SLA 任务共用线程池时,低延迟任务可能被高延迟任务(长任务)占线程拖慢,无抢占机制。问题:1) 长任务占线程,短延迟任务排队(队头阻塞);2) 无优先级(线程池队列通常 FIFO,不抢占);3) 高 SLA 任务被低 SLA 拖累。解决:1) 按 SLA 分级用不同线程池(高 SLA 独立池);2) 用优先级队列(PriorityBlockingQueue)但无抢占;3) 限制长任务并发;4) 高 SLA 任务用独立资源。核心是"按 SLA 隔离,避免高 SLA 被低 SLA 抢占"。

不同 SLA 共用池会互相拖累,应按 SLA 分级隔离池或用优先级控制。

#

117. 线程池在测试中的可控性,如何用固定线程池复现并发时序并断言任务执行顺序

线程池在测试中的可控性:如何用固定线程池复现并发时序并断言任务执行顺序?

  • 测试可控性
  • 固定线程池
  • 复现时序

测试可控性:1) 用单线程固定线程池(newSingleThreadExecutor)复现严格顺序,断言执行顺序;2) 用固定数量线程池(newFixedThreadPool(n))复现指定并发度;3) 用 CountDownLatch/Barrier 控制线程同步点,构造特定时序;4) 用可注入的 Executor(测试传可控池),替换生产池。断言顺序:用记录执行顺序的列表(阻塞队列)或 Mock 验证。核心:测试注入可控线程池 + 同步原语控制时序,断言执行顺序。

测试注入可控线程池 + 同步原语构造时序,断言顺序。单线程池保序便于断言。

#

118. 工作窃取算法在大任务与小任务混合下表现差异如何被压测验证

工作窃取算法在大任务与小任务混合下表现差异如何被压测验证?

  • 工作窃取
  • 大/小任务混合
  • 压测验证

工作窃取在大任务与小任务混合下:大任务耗时(占线程久),小任务被窃取/排队,负载均衡但大任务可能形成"尾延迟"。压测验证:1) 构造大任务(较长时间)与小任务(短时间)混合负载;2) 用 JMH 或压测框架测量吞吐、延迟分布(P50/P99)、任务完成时间;3) 对比不同混合比例(全大、全小、混合)下的工作窃取效率;4) 观察线程利用率与窃取次数。结论:混合负载下窃取均衡但大任务拖尾,验证延迟分布与利用率。

压测混合负载,测吞吐/P99/利用率,比较不同比例,验证工作窃取对大小任务的差异。