跳转到内容

死锁与并发问题定位

用途:线上出现线程挂住、吞吐骤降、偶发数据错乱时按此路径排查

互斥、持有并等待、不可剥夺、循环等待。破坏任意一条即可预防,工程上最实用的是破坏循环等待:全局固定加锁顺序。

其他形式:活锁(反复礼让导致无进展)、线程饥饿(低优先级或写者长期抢不到)、锁泄漏(异常路径未解锁)。

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