高频并发场景实战

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

1. 线程池参数如何根据业务类型(CPU 密集/IO 密集/混合)设定?动态线程池(如美团方案)解决什么痛点?

线程池参数如何根据业务类型(CPU 密集/IO 密集/混合)设定?动态线程池(如美团方案)解决什么痛点?

  • 线程数设定
  • 队列/拒绝策略
  • 动态线程池

线程池参数按业务类型:CPU 密集线程数≈核数+1,队列小(有界);IO 密集线程数≈核数×(1+等待比),队列可较大;混合任务按主要类型或分离池。动态线程池(美团动态线程池方案):运行时动态调整核心线程数、最大线程数、队列容量、拒绝策略、告警,无需重启。痛点:固定参数无法适应流量波动(高峰不够、低谷浪费),需压测/重启调参;动态线程池按监控自动调整,解决"参数不可调"与"调参需重启"。

静态参数按业务类型定,动态线程池按监控运行时调参,解决流量波动与调参重启痛点。

#
★★★

2. ThreadLocal 内存泄漏的完整链路(Entry 弱引用 key 与强引用 value)与线程池复用场景的脏数据风险?

ThreadLocal 内存泄漏的完整链路(Entry 弱引用 key 与强引用 value)与线程池复用场景的脏数据风险是什么?

  • Entry 弱引用 key
  • 强引用 value
  • 线程池脏数据

ThreadLocal 泄漏链路:ThreadLocalMap 的 Entry 用弱引用key(ThreadLocal),value 是强引用。ThreadLocal 被回收后 key 变 null,但 value 仍被 Entry 强引用,若线程长期存活(线程池),value 无法回收,内存泄漏。线程池复用脏数据:池线程复用,任务未 remove ThreadLocal,下一个任务读到上一个任务的 value(脏数据/上下文串扰)。应对:任务结束 remove();用不可变/清理。泄漏+脏数据是线程池 ThreadLocal 两大问题。

弱 key + 强 value 泄漏,线程池复用读脏数据。任务结束 remove 是根治。

#
★★★

3. 秒杀场景的并发控制,库存预扣、Redis Lua 原子操作与数据库乐观锁如何组合?

秒杀场景的并发控制:库存预扣、Redis Lua 原子操作与数据库乐观锁的组合是什么?

  • 库存预扣
  • Redis Lua 原子
  • 数据库乐观锁

秒杀并发控制三层:1) Redis Lua 原子操作:用 Lua 脚本原子地"检查库存+扣减"(INCR/DECR 原子),防止超卖,高并发限流;2) 库存预扣:先扣 Redis 库存,超卖用 Lua 拦截,异步落库;3) 数据库乐观锁:最终一致性用 UPDATE ... WHERE stock > 0 或版本号,防止数据库超卖。组合:Redis Lua 扛高并发(预扣),DB 乐观锁保证最终正确(兜底),配合限流/队列削峰。秒杀核心是"Redis 原子扣 + DB 乐观兜底"。

Redis Lua 原子扣减防高并发超卖,DB 乐观锁兜底最终一致。三层组合。

#
★★★

4. 分布式事务的并发控制,库存/余额场景的乐观锁与重试如何?

分布式事务的并发控制:库存/余额场景的乐观锁与重试是什么?

  • 乐观锁
  • 重试
  • 分布式事务

库存/余额场景并发控制:1) 乐观锁:UPDATE ... WHERE stock >= 扣减量(或 version=版本),影响行数=0 则失败;2) 重试:失败后重试(更新版本号),限次;3) 分布式事务:本地事务+消息(最终一致),或用 Seata 等。并发控制:乐观锁防超卖/超扣,重试处理竞争失败,幂等保证不重复扣。实践:乐观锁更新 + 版本号 + 限次重试 + 幂等键。库存/余额用乐观锁与重试保证并发正确。

乐观锁(WHERE 条件)+ 重试 + 幂等,控制库存/余额并发。分布式靠最终一致。

#
★★

5. 线程池长任务(>1 分钟)偶发 hang 时的系统化排查步骤(jstack / ThreadMXBean)?

线程池长任务(>1 分钟)偶发 hang 时的系统化排查步骤(jstack / ThreadMXBean)是什么?

  • 长任务 hang
  • jstack
  • ThreadMXBean

长任务 hang 排查步骤:1) 确认现象(任务卡住、长时间无完成);2) 多次 jstack 采样(间隔几秒),看卡住线程的栈(是否在 IO/锁/等待);3) ThreadMXBean 采样线程状态(BLOCKED/WAITING/RUNNABLE)与 CPU 时间,判断是阻塞还是死循环;4) 结合 JFR 看锁/IO 事件;5) 看堆(是否有 OOM/GC 停顿);6) 定位阻塞点(锁、下游、IO)。系统化:多次采样 + 状态/CPU 分析 + 环境排查。偶发 hang 用多次采样捕捉。

hang 排查:多次 jstack + ThreadMXBean 状态/CPU + JFR + 堆。定位阻塞点。

#
★★

6. 生产环境 CPU 飙高的排查全流程,top -Hp、jstack、线程号十六进制转换、火焰图如何串联?

生产环境 CPU 飙高的排查全流程:top -Hp、jstack、线程号十六进制转换、火焰图如何串联?

  • top -Hp
  • jstack
  • 十六进制转换

CPU 飙高排查流程:1) top 找高 CPU 进程(PID);2) top -Hp PID 找高 CPU 线程(TID);3) TID 转十六进制(printf %x);4) jstack PID 找对应线程栈(nid=0x十六进制),定位热点代码;5) 火焰图(perf/async-profiler)看整体 CPU 分布与热点栈。串联:进程→线程→线程栈→代码热点,火焰图补充全局视图。定位"哪个线程在哪个方法烧 CPU"。

top 找线程→十六进制转换→jstack 定位栈→火焰图全局。串联定位 CPU 热点。

#
★★

7. 生产环境频繁 Full GC 的排查路径,GC 日志、jmap 直方图、MAT 支配树如何逐步定位泄漏点?

生产环境频繁 Full GC 的排查路径:GC 日志、jmap 直方图、MAT 支配树如何逐步定位泄漏点?

  • GC 日志
  • jmap 直方图
  • MAT 支配树

频繁 Full GC 排查:1) GC 日志:确认 Full GC 频率、时长、堆(老年代)增长;2) jmap -histo:看对象直方图,找量大/增长的对象(候选泄漏);3) jmap dump 堆 + MAT 支配树:分析对象被谁引用(支配树找 GC 根路径),定位泄漏点(如 ThreadLocal 泄漏、静态缓存、连接未归还)。逐步:日志确认问题→直方图找候选→MAT 定位引用链。排查路径从宏观到微观。

GC 日志确认、jmap 直方图找候选、MAT 支配树定位引用链。逐步定位泄漏。

#
★★

8. 线上死锁的定位(jstack 死锁检测)与锁顺序/超时设计

线上死锁的定位(jstack 死锁检测)与锁顺序/超时设计是什么?

  • jstack 死锁检测
  • 锁顺序
  • 超时

线上死锁定位:jstack 输出含 "Found one Java-level deadlock",列出死锁线程的持有/等待锁链。设计预防:1) 锁顺序:所有线程按一致的全局顺序获取多锁(避免循环等待);2) 超时:用 tryLock(timeout) 获取锁,超时放弃并释放已持锁,避免无限等待;3) 减少持锁时间。定位(jstack 检测)+ 设计(锁顺序/超时)配合。死锁预防靠锁顺序与超时,定位靠 jstack。

死锁定位靠 jstack 检测,预防靠锁顺序(全序)+ tryLock 超时。

#
★★

9. CompletableFuture.allOf 在部分失败时的处理与降级策略设计?

CompletableFuture.allOf 在部分失败时的处理与降级策略设计是什么?

  • allOf
  • 部分失败
  • 降级

allOf 在部分 future 失败时:allOf 结果 CF 以异常完成(任一失败),但其他 future 仍执行。处理:1) 用 thenApply/exceptionally 处理整体失败(聚合);2) 每个 future 用 exceptionally 单独降级(失败返回默认值),使 allOf 不失败;3) handle 检查每个结果,部分失败部分降级。降级策略:放宽到"尽量完成"(失败项用默认值),或"全成或全败"(任一失败即整体失败)。设计:按业务容错程度,用 exceptionally 逐项降级或整体失败。

allOf 部分失败:逐项 exceptionally 降级,或整体失败。按容错度设计降级。

#
★★

10. ForkJoinPool 的工作窃取原理与 JDK 默认 pool 并行的性能拐点?

ForkJoinPool 的工作窃取原理与 JDK 默认 pool 并行的性能拐点是什么?

  • 工作窃取
  • 默认 pool
  • 性能拐点

工作窃取:空闲 worker 从其他 worker 队列窃取任务,均衡负载。JDK 默认 pool(commonPool)并行度=核数-1。性能拐点:1) 任务粒度:小于阈值任务窃取开销大;2) 任务数/并行度:超过核数后并行收益递减(CPU 密集);3) commonPool 被阻塞占满则性能下降。拐点:任务数超过核数(CPU 密集)或 commonPool 竞争时,吞吐不再随任务数增长。理解:并行收益受核数限制,粒度与阻塞影响拐点。

工作窃取均衡负载,性能拐点受核数、粒度、阻塞影响。commonPool 受核数限制。

#
★★

11. CompletableFuture 编排多个远程调用的异常传播与超时兜底设计?

CompletableFuture 编排多个远程调用的异常传播与超时兜底设计是什么?

  • 远程调用编排
  • 异常传播
  • 超时兜底

CF 编排多个远程调用:1) 并行发起(supplyAsync 多个);2) 异常传播:任一调用异常沿链传播(handle/exceptionally 处理);3) 超时兜底:每个调用 orTimeout/completeOnTimeout 设超时,超时降级默认值或失败;4) 聚合:allOf 等全部,thenApply 组合结果。设计:并行编排 + 每调用超时兜底 + 异常处理(降级/失败)。核心是"超时兜底 + 异常降级",防止单个慢调用拖垮整体。

多远程调用编排:并行 + 每调用 orTimeout 超时 + 异常降级。防慢调用拖垮。

#
★★

12. 缓存与数据库双写一致性,先更新库还是先删缓存,如何用延时双删/消息补偿?

缓存与数据库双写一致性:先更新库还是先删缓存,如何用延时双删/消息补偿?

  • 双写一致性
  • 先删缓存/先更新库
  • 延时双删/消息补偿

缓存与 DB 双写一致性:1) 先更新库再删缓存(推荐):读可能读到旧缓存,但删缓存后下次读新;2) 先删缓存再更新库:并发读可能写回旧值。问题:更新库后删缓存失败→缓存旧数据。方案:1) 延时双删:先删缓存,更新库,延时再删一次(防并发写回旧值);2) 消息补偿:删缓存失败的用 MQ 重试删除;3) 设置缓存过期兜底。核心是"先更库后删缓存 + 延时双删/消息补偿 + 过期兜底"。

双写一致性:先更库后删缓存,延时双删 + 消息补偿兜底,缓存过期兜底。

#
★★

13. 同步 RPC 调用改造为 MQ/事件异步后的数据一致性与补偿设计

同步 RPC 调用改造为 MQ/事件异步后的数据一致性与补偿设计是什么?

  • RPC 异步化
  • 数据一致性
  • 补偿

同步 RPC 改 MQ/事件异步:1) 数据一致性:本地事务 + MQ 发消息(本地消息表/事务消息),保证"业务变更与消息发送一致";2) 消费端幂等(重复消费去重);3) 补偿:消费失败重试、死信、对账补偿;4) 最终一致:异步流程最终一致,非实时。设计:本地事务+消息(发送一致)、幂等消费、重试/死信/对账补偿。异步化换取解耦与吞吐,代价是最终一致与补偿。

RPC 异步化用本地事务+消息保证一致,消费幂等,重试/死信/对账补偿。最终一致。

#
★★

14. 并发批量任务的拆分、进度上报与失败重试

并发批量任务的拆分、进度上报与失败重试是什么?

  • 任务拆分
  • 进度上报
  • 失败重试

并发批量任务:1) 拆分:大任务按分片/游标拆成子任务(线程池并行);2) 进度上报:用原子计数/游标记录完成进度,上报到监控/存储;3) 失败重试:子任务失败重试(限次),失败记录与补偿;4) 聚合:全部完成再聚合结果。实现:分片游标(原子)+ 进度计数器 + 重试(RetryTemplate)+ 结果聚合。批量任务并发执行需拆分、进度、重试、聚合。

批量任务拆分(分片)+ 进度上报(原子计数)+ 失败重试 + 聚合。并发执行四要素。

#

15. ThreadLocal 在线程池下的数据泄漏(InheritableThreadLocal vs TransmittableThreadLocal)?

ThreadLocal 在线程池下的数据泄漏(InheritableThreadLocal vs TransmittableThreadLocal)是什么?

  • ThreadLocal 泄漏
  • InheritableThreadLocal
  • TransmittableThreadLocal

线程池下 ThreadLocal 泄漏:池线程复用,任务未清理,值残留(脏数据)。InheritableThreadLocal:只在创建子线程时传递一次,线程池复用不继承(新任务用旧线程值),且可能泄漏。TransmittableThreadLocal:专门解决线程池传递,提交时捕获、执行时传递、执行后清理,无泄漏。选择:线程池上下文传递用 TransmittableThreadLocal(自动传递+清理),InheritableThreadLocal 不适合复用场景。泄漏=未清理+复用。

InheritableThreadLocal 只创建时传一次,线程池复用不适用;TransmittableThreadLocal 自动传递+清理,适合线程池。

#

16. 线程池参数调优,CPU 密集/IO 密集任务的核心线程数与队列策略如何选择?

线程池参数调优:CPU 密集/IO 密集任务的核心线程数与队列策略选择是什么?

  • 核心线程数
  • 队列策略
  • 调优

线程池调优:CPU 密集:核心线程数≈核数+1,队列用有界小队列(SynchronousQueue/小容量),避免线程过多切换;IO 密集:核心线程数≈核数×(1+等待比),队列有界较大(吸收波动),可配适当拒绝。队列策略:CPU 密集小队列(及时扩线程),IO 密集大队列(缓冲)。配合拒绝策略(CallerRuns/Abort)。核心:按任务类型定线程数+队列,保背压与吞吐。

CPU 密集≈核数+小队列,IO 密集≈核数×等待比+大队列。按类型定参数。

#

17. 分布式下的并发幂等,唯一索引、状态机与幂等键如何配合?

分布式下的并发幂等:唯一索引、状态机与幂等键如何配合?

  • 唯一索引
  • 状态机
  • 幂等键

分布式幂等:1) 唯一索引:数据库唯一索引(订单号/幂等键)防重复插入(冲突即重复);2) 幂等键:请求带幂等键,处理前检查(Redis SETNX/DB 唯一);3) 状态机:业务状态流转(如已支付→已发货),非法流转拒绝(防止重复处理)。配合:幂等键去重(唯一索引兜底)+ 状态机校验流转,保证重复请求不重复处理。分布式幂等用三层配合:幂等键+唯一索引+状态机。

幂等键 + 唯一索引 + 状态机配合,防重复处理。分布式幂等的标准方案。

#

18. 本地缓存与分布式缓存的分层,Caffeine + Redis 的两级缓存一致性如何?

本地缓存与分布式缓存的分层:Caffeine + Redis 的两级缓存一致性是什么?

  • 本地缓存
  • 分布式缓存
  • 两级一致性

两级缓存(Caffeine 本地 + Redis 分布式):读先查本地(快),未命中查 Redis(分布式),再未命中查 DB。一致性:1) 本地缓存与 Redis 不一致(多实例本地缓存);2) 更新时先更新 DB,再删 Redis,并通知其他实例失效本地缓存(MQ/广播);3) 本地缓存设短过期兜底。策略:更新 DB → 删 Redis → 广播失效本地 → 本地过期兜底。两级缓存一致性靠"删缓存+广播+过期"。

两级缓存一致性:更新 DB→删 Redis→广播失效本地→本地过期兜底。避免多实例本地不一致。

#

19. 线程池监控指标(活跃线程/队列积压/拒绝数)与告警阈值

线程池监控指标(活跃线程/队列积压/拒绝数)与告警阈值是什么?

  • 监控指标
  • 队列积压
  • 告警阈值

线程池监控指标:活跃线程数(active)、队列积压(queue.size)、拒绝数(rejected)、当前/最大线程数、任务耗时。告警阈值:1) 活跃率(active/max)> 80% 持续告警(接近饱和);2) 队列积压 > 容量 70-80% 告警(堆积风险);3) 拒绝数 > 0 告警(容量不足);4) P99 耗时超 SLA 告警。配合 Prometheus/Micrometer 暴露,阈值结合基线与去抖。监控与告警让容量问题提前暴露。

监控活跃率/队列/拒绝,阈值:活跃>80%、队列>70%、拒绝>0。提前预警容量。