当前位置: 首页 > news >正文

《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

💡核心结论:只要破坏四个条件中的任意一个,死锁就不可能发生。

常见死锁场景

  1. 嵌套锁(最经典):线程A持有锁1等待锁2,线程B持有锁2等待锁1
  2. 数据库连接池耗尽:所有连接被占用,新请求等待连接,已持有连接的线程又等待其他资源
  3. 生产者-消费者死锁:生产者等待消费者消费,消费者等待生产者生产
  4. 线程池中任务互相等待:子任务提交到同一线程池并等待结果

二、生活类比:从“十字路口”到“死锁”

死锁就像四个方向的车在十字路口互不相让

  • 每个方向都有一辆车(线程),占据了一条车道(资源)。
  • 每辆车都在等待前面那辆车让路(等待资源),但前面那辆车也在等更前面的车。
  • 四辆车形成了环形等待,谁也动不了——这就是死锁。

四大必要条件对应

  • 互斥:一条车道一次只能过一辆车。
  • 持有并等待:每辆车占着当前车道,同时等着前面车道空出来。
  • 不可剥夺:不能把前面的车强行拖走。
  • 循环等待:A等B,B等C,C等D,D等A。

排查死锁就像交警处理堵车

  • jstack:交警拿着手电筒逐一检查每辆车的状态和位置。
  • jconsole/VisualVM:交通指挥中心的大屏幕,一图看清所有车的堵塞关系。

预防死锁就像交通规则设计

  • 统一锁顺序:所有车按“先直行、再左转”的统一规则行驶。
  • 定时锁:每辆车设置“等待超时,先让其他车走”。
  • 减少锁粒度:把大十字路口拆成多个小路口,减少相互影响。

三、死锁代码示例

以下代码模拟了最经典的“两线程、两资源互持互等”死锁场景,是面试最常考的死锁案例:

publicclassDeadlockDemo{privatestaticfinalObjectLOCK_A=newObject();privatestaticfinalObjectLOCK_B=newObject();publicstaticvoidmain(String[]args){Threadt1=newThread(()->{synchronized(LOCK_A){System.out.println("线程1: 持有锁A,等待锁B...");try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized(LOCK_B){System.out.println("线程1: 获取锁B!");}}});Threadt2=newThread(()->{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-l12345

jstack 输出中定位死锁的关键信息

FoundoneJava-level deadlock:============================="Thread-1":waitingtolockmonitor0x000000076b8a3a00(object0x000000076c0019d8,ajava.lang.Object),which is held by"Thread-0""Thread-0":waitingtolockmonitor0x000000076b8a3b00(object0x000000076c0019e8,ajava.lang.Object),which is held by"Thread-1"Javastack informationforthe threads listed above:==================================================="Thread-1":atDeadlockDemo.lambda$main$1(DeadlockDemo.java:18)-waitingtolock<0x000000076c0019d8>(ajava.lang.Object)-locked<0x000000076c0019e8>(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 自带的图形化监控工具。

操作步骤

  1. 启动jconsole,连接目标 Java 进程
  2. 切换到“线程”页签
  3. 点击“检测死锁”按钮
  4. 如果存在死锁,工具会自动高亮死锁线程,并展示锁依赖关系

优势:一键检测,可视化展示锁依赖关系图,无需记忆命令行参数。

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 实现:使用ReentrantLocktryLock尝试获取所有锁,失败则释放已获取的锁。

LocklockA=newReentrantLock();LocklockB=newReentrantLock();publicbooleantryAcquireBoth(){if(lockA.tryLock()){try{if(lockB.tryLock()){returntrue;// 两把锁都拿到}}finally{if(!lockB.isHeldByCurrentThread()){lockA.unlock();// 获取B失败,释放A}}}returnfalse;}
5.3 破坏“不可剥夺”——使用可中断/超时锁

核心思想:使用ReentrantLocklockInterruptibly()tryLock(timeout),让线程在等待锁时可以被中断或超时放弃。

Locklock=newReentrantLock();try{if(lock.tryLock(3,TimeUnit.SECONDS)){try{// 业务逻辑}finally{lock.unlock();}}else{// 超时处理——放弃或重试}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}
5.4 破坏“互斥”——使用无锁/读写锁

互斥条件很难完全破坏——synchronizedReentrantLock本身就是互斥的。但在部分场景下可以通过以下方式缓解:

  • 读写锁ReentrantReadWriteLock):读锁共享,减少锁竞争
  • CAS 无锁Atomic*类):使用乐观锁替代悲观锁
  • CopyOnWrite 容器:读多写少场景
5.5 减少锁粒度
  • 使用ConcurrentHashMap替代HashMap+synchronized
  • 使用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 locklocked信息,定位代码行号。也可用jconsole或 VisualVM 的“检测死锁”按钮一键检测。

Q3:如何预防死锁?

核心是破坏四个必要条件之一:①破坏循环等待:所有线程按固定顺序获取锁;②破坏持有并等待:一次性申请所有资源,失败则全部释放;③破坏不可剥夺:使用ReentrantLock.tryLock(timeout)超时机制;④减少锁粒度:使用 ConcurrentHashMap 等并发容器。

Q4:synchronized 和 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只能检测synchronizedReentrantLock的死锁,无法检测更复杂的资源死锁(如数据库连接池耗尽)。

追问3:“数据库死锁和 Java 死锁有什么区别?”

👉 Java 死锁是 JVM 层面的线程锁竞争,由jstack排查。数据库死锁是事务级别的锁竞争(行锁、间隙锁),由数据库自己检测并回滚其中一个事务。两者互不直接关联——Java 代码中的synchronized不会导致 MySQL 死锁,反之亦然。


八、练习题

  1. 代码题:编写一个会产生死锁的 Java 程序,并使用jstack定位死锁线程和代码行号。

    💡 思路:两个线程以相反顺序获取两把锁,运行后用jps找 PID,jstack -l <PID>查看死锁信息。

  2. 分析题:某系统使用synchronized保护共享资源,偶尔出现线程卡死。如何在不重启的情况下确认是否发生了死锁?

    💡 思路:使用jstack -l <PID>生成线程快照,搜索deadlock关键字。如果确认死锁,分析锁顺序,考虑用ReentrantLock.tryLock(timeout)替代。

  3. 场景设计:一个支付系统需要同时锁定账户A和账户B进行转账。如何设计加锁策略避免死锁?

    💡 思路:按账户 ID 排序,总是先锁 ID 小的账户,再锁 ID 大的账户,破坏循环等待条件。或使用ReentrantLock.tryLock尝试获取两把锁,失败则全部释放并重试。


📊 你的学习进度

  • 当前:第56篇 / 共108篇 ·进阶篇:并发编程与JUC详解(第51~60篇)
  • ✅ 已完成:基础篇44篇 + 第45~56篇
  • 📖 正在学:第56篇
  • ⏳ 待学习:第57~108篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇


👉 下一篇文章预告

🚀下一篇:《第57篇:CompletableFuture异步编程》

内容简介:CompletableFuture 的核心 API(thenApply、thenCompose、allOf、anyOf)、异步任务编排、异常处理、超时控制与线程池配置。

👉多线程专题持续深入,拿下异步编程!

📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!

http://www.jsqmd.com/news/1185911/

相关文章:

  • 亲身探访长沙亨得利官方名表服务中心|官方电话及服务网点地址(2026年7月更新) - 亨得利官方博客
  • MAS-CN项目架构解析:开源Windows激活工具的代码实现原理
  • 海口净水器代理哪家好? - 中媒介
  • 从零到一:使用OpenCV级联分类器训练专属物体检测模型
  • Awesome-Prompt-Adapter-Learning-for-VLMs-CLIP项目概览:为什么这是VLM研究者的必备资源
  • 沈阳二手 LV 香奈儿出手避雷,合扬杜绝回收常见 “到手刀” 陷阱 - 开心测评
  • 社交网络分析实战:用Python构建可落地的关系决策地图
  • 高效R编程终极指南:如何让您的R代码运行速度提升10倍
  • 家庭游乐设备哪家专业? - 中媒介
  • Zeroth-01 Bot仿真到现实(Sim-to-Real)技术:原理与实现方法终极指南
  • 三步配置PotPlayer字幕翻译插件:免费实现外语视频无障碍观看终极指南
  • 三步实现Mac高效视频预览:QuickLook Video让Finder变身智能播放器
  • 仁怀别墅装修公司 - 中媒介
  • SmartCharts高级技巧:利用CRUD模板快速开发企业管理系统的实用指南
  • YOLOv8安全手套检测系统:从原理到工业部署实战
  • 2026北京卫生间防水、外墙、地下室、楼顶渗漏、阳光房防水 选对服务商,售后无忧,告别房屋渗漏(一次维修,售后无忧) - 企业资讯
  • Unity安卓权限管理避坑指南:从Manifest配置到多权限异步回调
  • Pandas多维聚合实战:金融风控中的七种生产级模式
  • 从零开始:使用ytDownloader轻松下载全网视频的完整指南
  • 爱彼中国官方售后服务中心|官方地址与维修热线权威信息通告(2026年7月最新) - 爱彼中国官方服务中心
  • 亲身到店探访海口亨得利官方名表服务中心|详细网点地址及服务电话(2026年7月更新) - 亨得利官方
  • 3步掌握RWTS-PDFwriter:macOS用户的终极PDF打印解决方案
  • SauronEye高级功能揭秘:多线程搜索和Office文件内容解析
  • CBAM注意力机制解析与实战应用
  • 肿瘤研究数据质量提升:FAIR原则落地的四大支柱与五项实操
  • Korpora CLI工具完全指南:命令行高效处理韩国语料库
  • 血红素原料哪家推荐? - 中媒介
  • 呼和浩特社区附近好喝的奶茶店 - 中媒介
  • 净水滤芯哪家发货快? - 中媒介
  • 天津珠宝定制哪家设计效果好? - 中媒介