JJava 知识库
JAVA INTERVIEW

高频面试题

Java多线程进阶约 3 分钟

线上服务突然有一批请求永久卡住,你怀疑 Java 死锁,会怎么快速确认、止损和修复?

参考回答约 3 分钟 · 口语表达
先说结论

我会先控制影响,再按证据定位:死锁需要互斥、占有且等待、不可抢占和循环等待同时成立。线上先保存多次线程转储,确认线程长期停在同一锁关系,并查看 JVM 是否报告 Java-level deadlock;修复通常通过统一锁顺序、缩小锁范围或超时获取锁破坏条件。

01

我会先确认影响范围,同时控制故障继续放大。从死锁四条件、线程转储、固定加锁顺序和超时获取锁系统分析。死锁需要互斥、占有且等待、不可抢占和循环等待同时成立。线上先保存多次线程转储,确认线程长期停在同一锁关系,并查看 JVM 是否报告 Java-level deadlock;修复通常通过统一锁顺序、缩小锁范围或超时获取锁破坏条件。 如果涉及数据库、分布式锁和 JVM 锁,还要把各层等待关系放在一起分析,单份 Java 堆栈可能看不到完整等待环。

02

止损之后,我会按请求链路建立证据,而不是凭经验猜。沿着「线程持有第一把锁 → 请求第二把锁 → 形成等待依赖边 → 依赖环闭合 → 检测并中断或重启恢复」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 线程持有第一把锁 死锁需要互斥、持有并等待、不可剥夺和循环等待四个条件同时成立。 请求第二把锁 线程在持有资源时请求另一资源,会在 wait-for graph 中形成有向边。 形成等待依赖边 多个锁顺序不一致最常见,数据库行锁、连接池与应用锁也可能跨层组成环。 ThreadMXBean#findDeadlockedThreads:JVM 可拥有同步器死锁检测。 jcmd Thread.print -l:锁拥有者与等待者现场。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。

03

定位时我最关注这些参数、指标和容量关系。用 CountDownLatch 固定两个线程反向拿锁,确认检测器识别;修复后循环 10 万次并注入 tryLock 超时。

04

找到根因后先做最小修复,再用同样的流量验证。两个线程分别锁定账户 A、B 后再请求对方账户,构成经典循环等待。按账户 ID 全局排序获取锁可以破坏环;同时增加 tryLock 超时与事务回滚,避免未来新增资源时无限等待。 锁顺序在不同代码路径不一致:BLOCKED 线程数:全局排序加锁。 锁内调用外部服务形成未知依赖:锁拥有者与等待者:锁内禁止远程 I/O。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「死锁线程数」为主基线,记录值应满足「必须 0」;同时保存 BLOCKED 线程数、锁拥有者与等待者,使后续变化能够回到同一时间轴比较。

05

恢复阶段要逐步放量,最后把监控和边界补齐。方案:更适合的场景:主要收益:代价与边界。 全局锁顺序:同类多资源加锁:从设计上消除循环等待:所有调用方必须遵守。 tryLock 超时:无法完全控制获取顺序:故障可恢复、避免无限卡住:需要回滚和重试幂等。 单线程所有者/消息串行化:按键操作可分区:不需要多锁组合:吞吐取决于分区且有排队。 选型至少带上 并发线程数、临界区长度、阻塞比例和任务到达速率,并用上面的量化基线验证;未知数据应明确为待测假设。

排查与恢复时间线从目标到落地
01线程持有第一把锁
02请求第二把锁
03形成等待依赖边
04依赖环闭合
05检测并中断或重启恢复