死锁与并发问题定位
用途:线上出现线程挂住、吞吐骤降、偶发数据错乱时按此路径排查
死锁四个必要条件
「死锁四个必要条件」小节链接互斥、持有并等待、不可剥夺、循环等待。破坏任意一条即可预防,工程上最实用的是破坏循环等待:全局固定加锁顺序。
其他形式:活锁(反复礼让导致无进展)、线程饥饿(低优先级或写者长期抢不到)、锁泄漏(异常路径未解锁)。
预防手段
「预防手段」小节链接| 手段 | 做法 |
|---|---|
| 锁顺序 | 约定全局顺序(如按资源 ID 排序后加锁) |
| 超时获取 | tryLock(timeout) 失败则回退重试,破坏“不可剥夺” |
| 缩小临界区 | 不在锁内做 IO/RPC;把远程调用移到锁外 |
| 单锁化 | 用不可变快照 + AtomicReference 替换,避免多锁 |
| 分层隔离 | 不同 SLA 用不同池与不同锁,减少交叉 |
定位路径(Java)
「定位路径(Java)」小节链接- 看线程栈:
jstack <pid>或kill -3(输出到标准日志)。搜索Found one Java-level deadlock与waiting to lock <0x...>。 - 对齐锁对象地址:A 持有 0x1 等 0x2,B 持有 0x2 等 0x1 → 典型环路。
- 看 BLOCKED/WAITING 数量分布:大量线程卡在同一把锁说明热点锁或锁内慢操作。
- 动态观察:Arthas 的
thread -b(找阻塞线程)、dashboard、profiler;async-profiler 的锁事件(-e lock)与 CPU 火焰图。 - 复现与固化:用压测或构造特定顺序复现,加断言/日志确认,再修复。
- 回归防护:为修复点加并发测试(多线程 + 屏障会合),或架构测试禁止跨层加锁。
其他并发症状的排查
「其他并发症状的排查」小节链接| 症状 | 常见原因 | 检查点 |
|---|---|---|
| 偶发读到旧值 | 缺少 happens-before | 是否 volatile/加锁/原子类 |
| 计数偏小 | 非原子自增 | i++、get+put 组合 |
| 吞吐周期性抖动 | 锁竞争 + GC | 锁等待时间、GC 日志、火焰图 |
| 队列持续增长 | 消费能力不足或线程池太小 | 活跃线程、任务等待时长、下游 RT |
| 线程数暴涨 | 无界线程创建或阻塞堆积 | 线程命名是否规范、栈顶在等什么 |
常见误区
「常见误区」小节链接| 误区 | 修正 |
|---|---|
| 加 sleep 让“时序对上了” | 掩盖竞态,必留隐患 |
| 用更大线程池解决阻塞 | 根因是锁内慢操作或下游慢 |
| 只在测试环境跑单线程验证 | 并发缺陷需要并发测试(多轮、随机延迟、屏障) |
| 认为 CAS 无锁就无问题 | ABA、自旋开销、复合条件仍需处理 |
延伸阅读
「延伸阅读」小节链接- 《Java 并发编程实战》第 3、10、11 章(死锁、活跃性问题、性能测试)
- Arthas / async-profiler 官方文档
- Burgin《Deadlock Avoidance and Detection》(经典综述)