《Java 100 天进阶之路》第56篇:死锁与排查工具(2026版) 第56篇死锁与排查工具2026版系列导航《Java 100 天进阶之路》完整目录 |⬅️ 上一篇第55篇线程池ThreadPoolExecutor |➡️ 下一篇第57篇CompletableFuture异步编程待发布️ 本文阅读地图3 分钟速览第55篇拿下了线程池但线程池用不好也会出问题——死锁就是最严重的线程阻塞事故。本篇彻底搞懂死锁的成因、排查和预防模块核心问题一句话回答死锁是什么为什么线程会“卡死”两个或多个线程互相持有对方需要的资源形成循环等待全部永久阻塞四大必要条件死锁怎么产生的互斥 持有并等待 不可剥夺 循环等待怎么排查线上死锁怎么定位jstack -l pid直接输出“Found one Java-level deadlock”怎么预防如何避免死锁破坏四大条件之一——统一锁顺序、tryLock超时、减少锁粒度面试最爱问高频考点有哪些见文末 面试 小节文章目录第56篇死锁与排查工具2026版️ 本文阅读地图3 分钟速览一、核心知识点二、生活类比从“十字路口”到“死锁”三、死锁代码示例四、死锁排查工具4.1 jstack —— 命令行排查首选4.2 jconsole —— 图形化排查4.3 VisualVM —— 深度分析4.4 排查工具对比五、死锁预防策略5.1 破坏“循环等待”——统一锁顺序5.2 破坏“持有并等待”——一次性申请所有资源5.3 破坏“不可剥夺”——使用可中断/超时锁5.4 破坏“互斥”——使用无锁/读写锁5.5 减少锁粒度六、生产级避坑清单七、面试高频考点面试官追问陷阱加分题八、练习题 你的学习进度 下一篇文章预告一、核心知识点死锁是什么死锁是指两个或多个线程在执行过程中因争夺共享资源而造成的一种互相等待的状态导致所有相关线程都无法继续执行。死锁是并发编程中最严重的故障之一——不会自动恢复只能人工干预。死锁 vs 活锁 vs 饥饿术语定义特点死锁Deadlock线程互相持有对方需要的资源永久阻塞不主动干预永不恢复活锁Livelock线程不断重试但总是失败无法推进线程不阻塞但“空转”饥饿Starvation低优先级线程长期抢不到资源线程有进展但极其缓慢死锁的四大必要条件Coffman条件死锁的发生必须同时满足以下四个条件缺一不可条件含义Java 中的体现互斥Mutual Exclusion资源同一时刻只能被一个线程占用synchronized / ReentrantLock 的排他性持有并等待Hold and Wait线程持有资源的同时还在等待其他资源嵌套 synchronized 块不可剥夺No Preemption线程持有的资源只能主动释放不能被强行剥夺Java 锁不支持外部强制解锁循环等待Circular Wait线程之间形成等待环路A等B、B等A核心结论只要破坏四个条件中的任意一个死锁就不可能发生。常见死锁场景嵌套锁最经典线程A持有锁1等待锁2线程B持有锁2等待锁1数据库连接池耗尽所有连接被占用新请求等待连接已持有连接的线程又等待其他资源生产者-消费者死锁生产者等待消费者消费消费者等待生产者生产线程池中任务互相等待子任务提交到同一线程池并等待结果二、生活类比从“十字路口”到“死锁”死锁就像四个方向的车在十字路口互不相让每个方向都有一辆车线程占据了一条车道资源。每辆车都在等待前面那辆车让路等待资源但前面那辆车也在等更前面的车。四辆车形成了环形等待谁也动不了——这就是死锁。四大必要条件对应互斥一条车道一次只能过一辆车。持有并等待每辆车占着当前车道同时等着前面车道空出来。不可剥夺不能把前面的车强行拖走。循环等待A等BB等CC等DD等A。排查死锁就像交警处理堵车jstack交警拿着手电筒逐一检查每辆车的状态和位置。jconsole/VisualVM交通指挥中心的大屏幕一图看清所有车的堵塞关系。预防死锁就像交通规则设计统一锁顺序所有车按“先直行、再左转”的统一规则行驶。定时锁每辆车设置“等待超时先让其他车走”。减少锁粒度把大十字路口拆成多个小路口减少相互影响。三、死锁代码示例以下代码模拟了最经典的“两线程、两资源互持互等”死锁场景是面试最常考的死锁案例publicclassDeadlockDemo{privatestaticfinalObjectLOCK_AnewObject();privatestaticfinalObjectLOCK_BnewObject();publicstaticvoidmain(String[]args){Threadt1newThread(()-{synchronized(LOCK_A){System.out.println(线程1: 持有锁A等待锁B...);try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized(LOCK_B){System.out.println(线程1: 获取锁B);}}});Threadt2newThread(()-{synchronized(LOCK_B){System.out.println(线程2: 持有锁B等待锁A...);try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized(LOCK_A){System.out.println(线程2: 获取锁A);}}});t1.start();t2.start();}}运行结果线程1:持有锁A等待锁B...线程2:持有锁B等待锁A...程序卡死永不退出四、死锁排查工具4.1 jstack —— 命令行排查首选jstack是 JDK 自带的命令行工具用于生成 Java 进程的线程快照Thread Dump。排查步骤# 1. 找到 Java 进程的 PIDjps-l# 输出12345 com.example.DeadlockDemo# 2. 生成线程快照-l 选项会额外打印锁信息jstack-l12345jstack 输出中定位死锁的关键信息FoundoneJava-level deadlock:Thread-1:waitingtolockmonitor0x000000076b8a3a00(object0x000000076c0019d8,ajava.lang.Object),which is held byThread-0Thread-0:waitingtolockmonitor0x000000076b8a3b00(object0x000000076c0019e8,ajava.lang.Object),which is held byThread-1Javastack informationforthe threads listed above:Thread-1:atDeadlockDemo.lambda$main$1(DeadlockDemo.java:18)-waitingtolock0x000000076c0019d8(ajava.lang.Object)-locked0x000000076c0019e8(ajava.lang.Object)关键信息解读Found one Java-level deadlock明确告知存在死锁waiting to lock当前线程正在等待哪把锁which is held by这把锁被哪个线程持有堆栈信息中的文件名和行号精准定位死锁代码位置生产环境最佳实践jstack -l pid中的-l选项会打印java.util.concurrent.locks的锁信息排查ReentrantLock死锁时必须加上。4.2 jconsole —— 图形化排查jconsole是 JDK 自带的图形化监控工具。操作步骤启动jconsole连接目标 Java 进程切换到“线程”页签点击“检测死锁”按钮如果存在死锁工具会自动高亮死锁线程并展示锁依赖关系优势一键检测可视化展示锁依赖关系图无需记忆命令行参数。4.3 VisualVM —— 深度分析VisualVM 是功能更强大的图形化工具集成了 jstack、jconsole 等工具的能力。核心功能线程状态实时监控死锁自动检测CPU/内存抽样分析线程 Dump 导出与对比适用场景本地开发和测试环境需要深入分析线程行为模式时。4.4 排查工具对比工具类型适用场景特点jstack命令行生产环境首选轻量、快速、可直接输出死锁信息jconsole图形化测试/预发布环境一键检测死锁可视化锁依赖VisualVM图形化深度分析功能最全面可追踪线程行为模式Arthas命令行生产环境高级排查阿里开源功能强大可在线诊断五、死锁预防策略根据“破坏四大必要条件”的思路有以下预防策略5.1 破坏“循环等待”——统一锁顺序核心思想所有线程按固定的全局顺序获取锁。示例规定必须先获取锁A再获取锁B。// ✅ 正确所有线程按相同顺序加锁publicvoidsafeMethod(){synchronized(LOCK_A){// 先Asynchronized(LOCK_B){// 后B// 业务逻辑}}}如果业务上无法统一顺序可采用资源编号法为所有资源分配唯一编号强制按编号升序获取。5.2 破坏“持有并等待”——一次性申请所有资源核心思想线程在开始执行前一次性申请所有需要的资源全部成功才执行否则释放已持有的资源并重试。Java 实现使用ReentrantLock的tryLock尝试获取所有锁失败则释放已获取的锁。LocklockAnewReentrantLock();LocklockBnewReentrantLock();publicbooleantryAcquireBoth(){if(lockA.tryLock()){try{if(lockB.tryLock()){returntrue;// 两把锁都拿到}}finally{if(!lockB.isHeldByCurrentThread()){lockA.unlock();// 获取B失败释放A}}}returnfalse;}5.3 破坏“不可剥夺”——使用可中断/超时锁核心思想使用ReentrantLock的lockInterruptibly()或tryLock(timeout)让线程在等待锁时可以被中断或超时放弃。LocklocknewReentrantLock();try{if(lock.tryLock(3,TimeUnit.SECONDS)){try{// 业务逻辑}finally{lock.unlock();}}else{// 超时处理——放弃或重试}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}5.4 破坏“互斥”——使用无锁/读写锁互斥条件很难完全破坏——synchronized和ReentrantLock本身就是互斥的。但在部分场景下可以通过以下方式缓解读写锁ReentrantReadWriteLock读锁共享减少锁竞争CAS 无锁Atomic*类使用乐观锁替代悲观锁CopyOnWrite 容器读多写少场景5.5 减少锁粒度使用ConcurrentHashMap替代HashMapsynchronized使用ConcurrentLinkedQueue替代手动加锁的队列避免在锁内调用外部方法可能触发未知阻塞六、生产级避坑清单✅ 死锁预防与排查避坑指南 1. 避免嵌套 synchronized → 能用同步块就别锁整个方法 2. 统一锁顺序 → 多个锁必须按固定顺序获取 3. 使用 ReentrantLock.tryLock(timeout) → 获取失败时释放已持有的锁并重试或降级 4. 减少锁持有时间 → 只在必要时加锁尽快释放 5. 避免在锁内调用外部方法 → 外部方法可能触发未知阻塞 6. 线程池任务中避免相互等待 → 子任务不要向同一线程池提交并等待结果 7. 线上排查三件套jps → jstack -l → 定位代码行号 8. 生产环境保留线程 Dump → 事故复盘必备线上死锁快速排查流程1. jps -l → 找到问题进程 PID 2. jstack -l PID → 生成线程快照搜索 deadlock 3. 定位代码行号 → 找到 waiting to lock 和 locked 对应的行号 4. 分析锁顺序 → 检查两个线程获取锁的顺序是否相反 5. 修复并验证 → 统一锁顺序或用 tryLock 替代七、面试高频考点Q1死锁的四个必要条件是什么死锁必须同时满足四个条件①互斥资源一次只能被一个线程占用②持有并等待线程持有资源的同时还在等待其他资源③不可剥夺线程持有的资源只能主动释放不能被强行剥夺④循环等待线程之间形成等待环路。只要破坏任意一个死锁就不会发生。Q2如何排查 Java 死锁① 使用jps找到 Java 进程 PID② 执行jstack -l PID生成线程快照③ 查找输出中的Found one Java-level deadlock④ 分析waiting to lock和locked信息定位代码行号。也可用jconsole或 VisualVM 的“检测死锁”按钮一键检测。Q3如何预防死锁核心是破坏四个必要条件之一①破坏循环等待所有线程按固定顺序获取锁②破坏持有并等待一次性申请所有资源失败则全部释放③破坏不可剥夺使用ReentrantLock.tryLock(timeout)超时机制④减少锁粒度使用 ConcurrentHashMap 等并发容器。Q4synchronized 和 ReentrantLock 在死锁预防上有什么区别synchronized 无法中断、无法超时发生死锁后只能通过jstack发现并重启。ReentrantLock 支持tryLock(timeout)和lockInterruptibly()可以通过超时中断打破死锁在预防死锁上更灵活。Q5活锁和死锁有什么区别死锁是线程互相等待资源永久阻塞不主动干预永不恢复。活锁是线程不阻塞但不断重试失败无法推进任务虽然“活着”但“空转”。活锁通常通过引入随机退避如指数退避来解决。面试官追问陷阱加分题追问1“使用ReentrantLock.tryLock()一定能避免死锁吗”不一定。tryLock()只能避免无限等待导致的死锁但如果业务逻辑要求“两把锁必须同时持有才能操作”使用tryLock后需要正确处理“部分获取成功”的情况——获取锁A成功但获取锁B失败时必须释放锁A并重试否则仍可能死锁。追问2“jstack输出中的Found one Java-level deadlock一定准确吗”不一定。jstack只能检测到已经发生的死锁。有些死锁是间歇性的如特定时序下触发jstack抓取时可能恰好没有发生。而且jstack只能检测synchronized和ReentrantLock的死锁无法检测更复杂的资源死锁如数据库连接池耗尽。追问3“数据库死锁和 Java 死锁有什么区别” Java 死锁是 JVM 层面的线程锁竞争由jstack排查。数据库死锁是事务级别的锁竞争行锁、间隙锁由数据库自己检测并回滚其中一个事务。两者互不直接关联——Java 代码中的synchronized不会导致 MySQL 死锁反之亦然。八、练习题代码题编写一个会产生死锁的 Java 程序并使用jstack定位死锁线程和代码行号。 思路两个线程以相反顺序获取两把锁运行后用jps找 PIDjstack -l PID查看死锁信息。分析题某系统使用synchronized保护共享资源偶尔出现线程卡死。如何在不重启的情况下确认是否发生了死锁 思路使用jstack -l PID生成线程快照搜索deadlock关键字。如果确认死锁分析锁顺序考虑用ReentrantLock.tryLock(timeout)替代。场景设计一个支付系统需要同时锁定账户A和账户B进行转账。如何设计加锁策略避免死锁 思路按账户 ID 排序总是先锁 ID 小的账户再锁 ID 大的账户破坏循环等待条件。或使用ReentrantLock.tryLock尝试获取两把锁失败则全部释放并重试。 你的学习进度当前第56篇 / 共108篇 ·进阶篇并发编程与JUC详解第51~60篇✅ 已完成基础篇44篇 第45~56篇 正在学第56篇⏳ 待学习第57~108篇 完整目录 学习指南 | 订阅本专栏不错过每一篇 下一篇文章预告下一篇《第57篇CompletableFuture异步编程》内容简介CompletableFuture 的核心 APIthenApply、thenCompose、allOf、anyOf、异步任务编排、异常处理、超时控制与线程池配置。多线程专题持续深入拿下异步编程《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇建议收藏 关注一起100天拿offer 点击关注我更新后第一时间收到推送