日期:2026-10-17
总题数:1 题(默认单题)
方向:性能优化
场景:某电商订单查询服务(8C16G,JDK17 + Spring Boot),大促期间 QPS 从 2000 涨到 6000。监控发现:
synchronized 保护的本地缓存刷新点 + 一个几乎每请求都 +1 的 AtomicLong 计数器jstack 显示大量线程 BLOCKED,集中在同一个 monitor 上3 个问题:
1首先根据已知的信息大量blocked以及sys高,应该是锁导致的线程阻塞;排查可以查看skywalking和接口rt监控监控确认具体的接口和出现缓慢的节点位置; 通过jstack导出当前线程栈,查看blocked的线程情况; 2 基于热点更新是一个计数器和一个synchronized保护的缓存更新,检查下为什么会有那么多内容进行缓存刷新,怀疑是缓存击穿,使用非常规数据去攻击缓存导致的;可以对访问的账号 ,ip,区域进行检查,看是否是某些账户的恶意攻击;对其进行封禁,或者策略处理,另外后续可以增加布隆过滤器对不存在的key进行过滤,前置进行拦截; 如果是正常缓存,那么应该就是设计问题,要么缓存个活期时间太短,要么大量不同数据逐个刷新导致,可以做缓存预热,缓存保护,防止问题; 上线后增加关于缓存的管理,以及做横向扩容qps太高了要降低单机负载
得分:2.5 / 5
优点:
sys 高与阻塞的关联判断到位不足:
park/unpark → futex 系统调用 → 内核态开销 → sys 飙高,这条因果链是本题核心;量化手段也缺(pidstat -w 看 cswch、vmstat 看 cs/r、top -Hp 定位热点线程、perf top、arthas thread -n)AtomicLong:高并发 CAS 自旋 + 伪共享(false sharing)是 sys/user 双高的另一半原因,回答完全没提补充:详见下方「完整讲解」。
先建立因果链,再谈工具:
高并发争抢同一个 monitor
│
▼
synchronized 膨胀为重量级锁 → 未获锁线程 ObjectMonitor::enter
│
▼
线程 park/unpark → JVM 调用 futex 系统调用(内核态)
│
▼
大量上下文切换(cs 暴增)+ 可运行队列变长
│
▼
CPU sys 占比飙升 + load average 飙升
关键认知:
BLOCKED(等待锁)其实是 S 状态,不直接计入 load;但锁释放后线程被唤醒争抢,出现"唤醒风暴 + 自旋",R 队列被挤爆,load 随之飙升。futex(FUTEX_WAIT/FUTEX_WAKE)。锁竞争越激烈,park/unpark 越频繁,futex 调用越多。AtomicLong 的额外贡献:8 核上所有线程 CAS 同一个 value,缓存行在核间来回失效(false sharing / cache line bouncing),失败线程自旋重试 → user 高 + 无谓消耗;若该字段与相邻热点字段共享缓存行,伪共享进一步放大。定位路径(落到命令与指标):
| 步骤 | 命令 | 看什么 |
|---|---|---|
| 1. 确认 CPU/负载构成 | top → 1(分核)、uptime | %sy 高、load 与核数比 |
| 2. 上下文切换速率 | vmstat 1 / pidstat -w 1 -p <pid> | cs(context switch/s)、cswch/s(自愿切换)暴增 |
| 3. 定位热点线程 | top -Hp <pid> → 转十六进制 printf '%x' <tid> | 找到吃 CPU 的线程 |
| 4. 线程栈深挖 | jstack <pid> > stack.txt、arthas thread -b | 大量 BLOCKED (on object monitor) 指向同一地址;thread -b 直接给"阻塞其他线程的元凶" |
| 5. 锁竞争量化 | arthas monitor -c 5 com.xxx.Class method、JFR(-XX:StartFlightRecording)锁事件 | 阻塞次数/平均阻塞耗时 |
| 6. 内核态采样 | perf top -p <pid>、perf record -g | futex、__lll_lock_wait、sys_futex 占比 |
| 7. 应用层链路 | SkyWalking / 接口 RT 监控 | 确认慢在哪个接口、哪个节点(题中回答已覆盖) |
题中回答的 skywalking + jstack 只是第 4、7 步;缺了第 2、3、5、6 步,所以无法量化"锁竞争 → 系统调用"的因果。
| 层 | 手段 | 预期效果 |
|---|---|---|
| 锁粒度 | ① 缩小临界区,把非共享操作移出 synchronized;② 读写分离:缓存刷新用 volatile + 不可变快照替换(读无锁,写整体替换引用) | 临界区变短 → 锁持有时间下降 → 阻塞线程数骤减 → cs 下降 |
| 无锁化 | ① AtomicLong → LongAdder(Striped64 分段 CAS,把竞争分散到多个 cell);② 缓存行填充 @Contended(需 -XX:-RestrictContended)解决伪共享;③ 单写者模式 | CAS 失败自旋大幅减少;消除 cache line bouncing → user/sys 双降,吞吐提升 |
| 线程模型 | ① 同步刷新 → 异步批量刷新(定时任务 / single-flight 合并重复刷新请求);② 缓存刷新交给独立单线程(single-writer),业务线程只读;③ 若计数无需强一致,改为本地聚合 + 定时刷库 | 从"每请求争锁"变"批量/单写者",争抢源消失 |
LongAdder 原理:AtomicLong 的 value 是单点 volatile,所有线程 CAS 同一地址;LongAdder 维护 base + Cell[],线程按 threadLocalRandomProbe 散列到不同 cell,冲突时扩容,求和时才汇总。8 核下写吞吐可提升数倍。
sys%、cs/s、cswch/s、load1/5/15、锁阻塞时间(ThreadMXBean.getThreadInfo 的 blockedCount/blockedTime、JFR 锁事件)、LongAdder 所在监控项cs/s 突增、sys% > 40%、jstack BLOCKED 线程数 > X 时触发monitor/watch 常驻排查;JFR 采样回放;SkyWalking 慢接口拓扑| 轮次 | 方向 | 难度 | 表现 | 得分 |
|---|---|---|---|---|
| R1 | 性能优化 | 高阶 | 锁竞争方向判断正确;但根因链、量化定位、三层优化均缺位,方向跑偏到缓存 | 2.5 / 5 |
pidstat -w / vmstat / perf / arthas thread -b 等工具链LongAdder、@Contended、cache line bouncingAtomicLong),不要被无关假设带偏LongAdder vs AtomicLong、伪共享、futex 调度机制synchronized 锁升级路径与重量级锁的 park/unpark 代价面试官:出题 R1(性能优化 · 高阶),3 个子问题,命中上述场景。
候选人回答(原文保留):
1首先根据已知的信息大量blocked以及sys高,应该是锁导致的线程阻塞;排查可以查看skywalking和接口rt监控监控确认具体的接口和出现缓慢的节点位置; 通过jstack导出当前线程栈,查看blocked的线程情况; 2 基于热点更新是一个计数器和一个synchronized保护的缓存更新,检查下为什么会有那么多内容进行缓存刷新,怀疑是缓存击穿,使用非常规数据去攻击缓存导致的;可以对访问的账号 ,ip,区域进行检查,看是否是某些账户的恶意攻击;对其进行封禁,或者策略处理,另外后续可以增加布隆过滤器对不存在的key进行过滤,前置进行拦截; 如果是正常缓存,那么应该就是设计问题,要么缓存个活期时间太短,要么大量不同数据逐个刷新导致,可以做缓存预热,缓存保护,防止问题; 上线后增加关于缓存的管理,以及做横向扩容qps太高了要降低单机负载
面试官点评:得分 2.5/5。方向正确但深度不足,遗漏 AtomicLong 强信号,三层优化未答(详见上方点评与完整讲解)。
说明:本次会话按daily-interview-question新默认(单题)只抽取 1 题。
生成时间:2026-10-17 · 由 daily-interview-question 维护