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

孤舟笔记 并发篇六 死锁是怎么产生的?面试必问的四个条件和三种破解方法

文章目录

    • 先说结论:死锁的四个必要条件
    • 死锁是怎么产生的?一个经典翻车现场
    • 四个必要条件:缺一个都不会死锁
    • 如何避免死锁?三种实用策略
      • 策略一:固定加锁顺序(破坏循环等待)
      • 策略二:一次性获取所有锁(破坏请求与保持)
      • 策略三:设置超时时间(破坏不剥夺)
    • 死锁检测:程序卡住了怎么排查?
    • 死锁全景
    • 回答技巧与点评
        • 标准回答
        • 加分回答
        • 面试官点评

个人网站

你有没有遇到过这种情况——程序跑着跑着就"卡死了",不报错、不退出,就是一动不动。重启就好,过会儿又卡。最诡异的是,CPU 和内存都正常,线程却全部"罢工"了。

如果你仔细查日志,大概率会看到两个线程互相等对方释放锁——这就是死锁。面试官特别爱问"死锁的四个必要条件",但只会背条件还不够,你得知道怎么避免、怎么检测、怎么修复。

先说结论:死锁的四个必要条件

||| 维度 | 说明 |
|||------|------|
||| 互斥条件 | 资源同一时刻只能被一个线程占用 |
||| 请求与保持 | 拿着锁不放手,又去申请别的锁 |
||| 不剥夺条件 | 线程已获得的锁不能被强行抢占 |
||| 循环等待 | 多个线程形成环形等待链:A 等 B,B 等 A |
||| 死锁产生 | 四个条件同时满足才会死锁 |
||| 避免策略 | 破坏其中任意一个条件即可 |

一句话记住:死锁就像两个人过独木桥,谁都不肯退,谁也过不去——得有人先让步。

死锁是怎么产生的?一个经典翻车现场

最经典的死锁场景:两个线程,两把锁,顺序反了。

// 线程 A:先锁资源1,再锁资源2synchronized(lock1){// 👈 拿到 lock1Thread.sleep(100);// 给线程 B 时间去拿 lock2synchronized(lock2){// 👈 等待 lock2...但 lock2 在线程 B 手里// 执行业务}}// 线程 B:先锁资源2,再锁资源1synchronized(lock2){// 👈 拿到 lock2Thread.sleep(100);// 给线程 A 时间去拿 lock1synchronized(lock1){// 👈 等待 lock1...但 lock1 在线程 A 手里// 执行业务}}

线程 A 拿着 lock1 等 lock2,线程 B 拿着 lock2 等 lock1。你等我,我等你,谁都不放手,程序就"死"了。

用生活类比:两个人面对面过独木桥,谁都不肯退回去让对面先过,结果谁也过不了。

四个必要条件:缺一个都不会死锁

Coffman 总结的死锁四个必要条件,面试必考:

  1. 互斥:锁本身就是互斥的,这个条件很难破坏——锁不就是用来互斥的吗?
  2. 请求与保持:拿着锁 A 不放,又去申请锁 B。这是最容易出问题的地方。
  3. 不剥夺:Java 的synchronized不支持强行剥夺,拿到锁就只能等它自己释放。
  4. 循环等待:A 等 B,B 等 C,C 又等 A,形成闭环。

关键点:四个条件必须同时满足。破坏其中任何一个,死锁就不会发生。

如何避免死锁?三种实用策略

策略一:固定加锁顺序(破坏循环等待)

最简单也最有效。所有线程按同一顺序获取锁,就不会形成环路。

// 所有线程都先锁 lock1,再锁 lock2 👈synchronized(lock1){synchronized(lock2){// 执行业务}}

生活类比:所有车辆靠右行驶,不会迎面相撞。

策略二:一次性获取所有锁(破坏请求与保持)

先拿到所有需要的锁,再开始干活。拿不全就释放已有的,重试。

synchronized(LockManager.class){// 先用一个全局锁"预约"synchronized(lock1){synchronized(lock2){// 三个锁都拿到了,开始干活 👈}}}

策略三:设置超时时间(破坏不剥夺)

ReentrantLocktryLock(timeout)代替synchronized,超时获取不到锁就放弃。

Locklock1=newReentrantLock();Locklock2=newReentrantLock();try{if(lock1.tryLock(100,TimeUnit.MILLISECONDS)){// 👈 超时放弃try{if(lock2.tryLock(100,TimeUnit.MILLISECONDS)){try{// 执行业务}finally{lock2.unlock();}}}finally{lock1.unlock();}}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}

拿不到锁就放弃,不死等——这就是tryLock的价值。

死锁检测:程序卡住了怎么排查?

如果线上真的死锁了,别慌。JDK 自带工具就能检测:

jstack<pid># 打印线程堆栈,会直接告诉你"Found one Java-level deadlock"jconsole# 图形界面,线程页签可检测死锁

jstack的输出会明确告诉你哪些线程死锁了,各自持有和等待的锁是什么,排查起来非常快。

死锁全景

死锁 全景 四个必要条件(Coffman) ├── 互斥条件 ── 资源独占,不可共享 ├── 请求与保持 ── 拿着锁不放手,又申请新锁 ├── 不剥夺条件 ── 锁不能被强行抢占 └── 循环等待 ── A 等 B,B 等 A,形成环路 三种避免策略 ├── 固定加锁顺序 ── 破坏循环等待(最推荐) ├── 一次性获取所有锁 ── 破坏请求与保持 └── tryLock 超时机制 ── 破坏不剥夺条件 检测工具 ├── jstack <pid> ── 命令行检测死锁 └── jconsole ── 图形界面检测死锁 口诀:互斥请求不剥夺,循环等待四条件, 固定顺序最有效,tryLock 超时也安全。

回答技巧与点评

标准回答

死锁是两个或多个线程互相等待对方持有的锁,导致所有线程都无法继续执行的现象。死锁产生需要同时满足四个必要条件:互斥、请求与保持、不剥夺、循环等待。避免死锁的常用方法有:固定加锁顺序(破坏循环等待)、一次性获取所有锁(破坏请求与保持)、使用 tryLock 设置超时(破坏不剥夺条件)。检测死锁可使用 jstack 或 jconsole。

加分回答
  1. 设计原则:死锁的本质是"资源竞争 + 错误的获取顺序"。从设计上尽量减少锁的嵌套层次,缩小锁的粒度,从根本上降低死锁概率
  2. 边界情况:不只是两个线程会死锁,多个线程形成环形等待链也会。此外,synchronized不支持超时和中断,死锁后只能杀进程;ReentrantLocktryLocklockInterruptibly提供了更灵活的退出机制
  3. 实际应用:数据库也有死锁问题,MySQL 的 InnoDB 引擎通过死锁检测(wait-for graph)自动回滚代价最小的事务。分布式场景中,Redis 分布式锁也要考虑死锁,通常用过期时间 + 续期机制解决
面试官点评

这道题考的是你对并发安全问题系统性分析的能力。只会背四个条件只能拿基础分。能说出每种避免策略对应破坏哪个条件、synchronizedReentrantLock在死锁处理上的差异、以及实际排查手段,才能拿高分。如果你还能延伸到数据库和分布式场景的死锁,面试官会认为你理解深度足够。

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

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

相关文章:

  • 14.深入YOLOv8:CSPDarknet/C2f原理+车辆检测实战+部署优化全攻略
  • Python和Java默认排序算法TimSort,为什么它比快排和堆排更受青睐?
  • SCI/SSCI投稿避坑指南:Cover Letter里这5个细节没写对,编辑可能直接拒稿
  • 【深度解析】从 GPT-5.5 Codex 到百万 Token 上下文:构建可落地的多模型 AI Coding Agent 路由架构
  • 视界新生,多模态破壁 ——DeepSeek 识图模式正式上线
  • 【navicat不安装sql server直接远程连接服务器数据库】
  • ARM MPAM架构解析:资源隔离与QoS控制技术
  • 【深度解析】从人形机器人到 AI 数字分身:可信“合成人”背后的多模态智能架构与工程落地
  • 大语言模型安全对齐与拒绝行为优化实践
  • VLA模型动作退化问题与DUALVLA解决方案
  • PHP开发者速看:Laravel 12原生AI驱动架构详解(内置AI Service Container深度拆解)
  • FlexASIO终极指南:免费解锁Windows专业级低延迟音频体验
  • 有机富硒大米核心技术拆解及靠谱品牌实测推荐:控糖控碳水大米,有机五常大米,有机大米价格,有机大米标准,排行一览! - 优质品牌商家
  • VMware Workstation Pro 17 免费激活终极指南:获取数千个有效许可证密钥的完整教程
  • 从F-22到你的笔记本:揭秘那些藏在消费电子里的“隐形”吸波材料(橡胶垫/泡棉选购指南)
  • 2026 文档解析工具终极选型指南:MinerU vs LlamaParse vs Docling vs Unstructured vs PyMuPDF
  • Tiny-Twin:低成本CPU架构实现5G数字孪生信道仿真
  • 2026年ai智慧图书馆top5推荐:图书馆管理云平台,图书馆自动化管理系统,图书馆自助借还书机,排行一览! - 优质品牌商家
  • 商米港股上市:市值超370亿港元 中专生林喆敲钟 小米浮盈20亿
  • 告别电流采样:用SimpleFOC库实现无感FOC电机控制的保姆级配置流程
  • STM32F4实战:用CubeMX配置SDIO+DMA读写SD卡,附完整代码与常见问题排查
  • 大模型路由技术:智能调度实现成本与性能优化
  • MySQL8四大事务隔离级别详解,彻底搞懂脏读、不可重复读、幻读
  • 【深度解析】Open Design:用本地优先架构重塑 AI UI 生成工作流
  • QT实战:如何用QProcess打造一个带界面的cmd工具(附完整源码)
  • 用OpenCvSharp搞定工业零件涂胶检测:一个C#工程师的实战踩坑与调参心得
  • 如何快速解决Windows热键冲突:完整检测与优化指南
  • 【独家首发】Swoole+LLM双通道保活协议设计(心跳+语义校验+上下文快照):附可商用的376行核心源码及压力测试报告
  • 智能测试系统:LLM如何提升软件测试效率与覆盖率
  • 2026年小白程序员必看:轻松上手AI,收藏这份从0到1学习指南