【Java踩坑笔记】51_接口响应突然变慢,我们这样定位到了死锁

📅 2026/7/17 21:40:43
【Java踩坑笔记】51_接口响应突然变慢,我们这样定位到了死锁
51 | 接口响应突然变慢我们这样定位到了死锁摘要死锁的典型症状是接口突然变慢或卡死jstackDump 线程堆栈后搜索「Found one Java-level deadlock」快速定位。一、问题现象生产环境平时 RT 20ms 的接口突然变成 5000ms且持续恶化。重启后恢复正常但运行数小时后又复现。二、排查步骤步骤 1拿到线程堆栈# 找到 Java 进程 PIDjps-l# Dump 线程堆栈最重要的一步jstackpidthread_dump.txt步骤 2在 Dump 文件里搜索死锁grep-A20deadlockthread_dump.txt# 或者grepFound one Java-level deadlockthread_dump.txt典型的死锁报告Found one Java-level deadlock: Thread-23: waiting to lock monitor 0x00007f8a400 (object 0x00007f8a480, a java.lang.Object), which is held by Thread-45 Thread-45: waiting to lock monitor 0x00007f8b200 (object 0x00007f8b280, a java.lang.Object), which is held by Thread-23步骤 3在代码里找对等的锁获取顺序// ❌ 死锁根因两个方法锁顺序相反classServiceA{publicvoidprocessA(){synchronized(LOCK_X){synchronized(LOCK_Y){...}}}}classServiceB{publicvoidprocessB(){synchronized(LOCK_Y){// ❌ 顺序相反synchronized(LOCK_X){...}}}}三、原理解析3.1 死锁的四个必要条件条件说明互斥锁不能共享占有且等待持有一把锁等另一把不可抢占锁不能被强制释放循环等待A 等 BB 等 A破坏任意一个条件即可避免死锁。3.2 如何破坏循环等待统一锁顺序最有效// ✅ 所有方法都按相同的顺序获取锁classServiceA{publicvoidprocessA(){synchronized(LOCK_X){// 先 Xsynchronized(LOCK_Y){...}// 再 Y}}}classServiceB{publicvoidprocessB(){synchronized(LOCK_X){// ✅ 顺序统一synchronized(LOCK_Y){...}}}}四、正确写法4.1 用tryLock(timeout)超时释放// ✅ 用 tryLock 超时机制避免永久等待ReentrantLocklockXnewReentrantLock();ReentrantLocklockYnewReentrantLock();publicvoidprocess(){booleangotXfalse;booleangotYfalse;try{gotXlockX.tryLock(5,TimeUnit.SECONDS);if(!gotX)return;// 超时放弃gotYlockY.tryLock(5,TimeUnit.SECONDS);if(!gotY)return;// 业务逻辑}catch(InterruptedExceptione){Thread.currentThread().interrupt();}finally{if(gotY)lockY.unlock();if(gotX)lockX.unlock();}}4.2 用单把锁代替多把锁// ✅ 减少锁数量从根源避免死锁privatefinalObjectGLOBAL_LOCKnewObject();publicvoidprocessA(){synchronized(GLOBAL_LOCK){...}}publicvoidprocessB(){synchronized(GLOBAL_LOCK){...}}4.3 数据库死锁按 ID 排序后更新// ❌ 死锁并发更新时锁顺序不确定UPDATEaccountSETbalancebalance-100WHEREid1;UPDATEaccountSETbalancebalance100WHEREid2;// ✅ 按 ID 排序后更新统一顺序ListLongidsList.of(2L,1L).stream().sorted().toList();for(Longid:ids){UPDATEaccountSET...WHEREid?;}五、最佳实践✅ 避免死锁的 4 条铁律所有地方按相同顺序获取锁破坏循环等待用tryLock(timeout)代替lock()超时自动释放减少锁的数量能用单把锁就不用两把数据库更新按 ID 排序避免行锁死锁 在线定位死锁# 持续监控每 10 秒 Dump 一次watch-n10jstack pid thread_$(date%s).txt# 用 VisualVM / JConsole 连接实时看线程状态六、小结死锁症状接口 RT 持续恶化重启后暂时恢复排查核心jstack pidDump 堆栈搜索deadlock根因锁获取顺序不一致形成循环等待修复统一锁顺序或用tryLock(timeout)超时释放数据库死锁按 ID 排序后更新下一篇终篇总结篇Java 踩坑的 10 条黄金法则—— 所有坑的底层规律用 10 句话记住。