news 2026/9/11 7:12:26

死锁:线程卡死不是偶然,而是设计问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
死锁:线程卡死不是偶然,而是设计问题

网罗开发(小红书、快手、视频号同名)

大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


文章目录

    • 前言
    • 什么是死锁?用最直观的方式理解
      • 1. 一个最经典的死锁 Demo(可直接运行)
      • 2. 这段代码到底发生了什么?
    • 真实业务中,死锁通常长什么样?
      • 常见场景 1:订单 + 库存
      • 常见场景 2:缓存 + 数据库
    • 解决方案一:按固定顺序加锁(最重要)
      • 1. 原则很简单
      • 2. 改造上面的死锁代码
      • 3. 为什么这种方式最靠谱?
    • 解决方案二:使用 tryLock,避免无限等待
      • 1. 核心思想
      • 2. 示例代码(可运行)
      • 3. tryLock 的真实价值
    • 解决方案三:使用并发包,减少“自己加锁”
      • 1. 示例:用 ConcurrentHashMap 替代 synchronized Map
      • 2. 为什么并发包更安全?
    • 一些非常实用的经验总结
    • 总结

前言

只要你写过稍微复杂一点的并发代码,大概率都遇到过这种情况:

  • 程序不报错
  • CPU 占用不高
  • 日志也不动
  • 但是服务就是「卡住了」

十有八九,你遇到的是死锁

死锁本身并不神秘,本质只有一句话:

多个线程互相等待对方持有的锁资源,谁也不肯先放手。

但真正的问题在于——
死锁往往不是一行代码的问题,而是整体设计的问题。

下面我们一步一步来拆。

什么是死锁?用最直观的方式理解

先别急着上概念,我们直接看代码。

1. 一个最经典的死锁 Demo(可直接运行)

下面这段代码,几乎是所有死锁案例的「祖师爷」。

publicclassDeadLockDemo{privatestaticfinalObjectlockA=newObject();privatestaticfinalObjectlockB=newObject();publicstaticvoidmain(String[]args){Threadt1=newThread(()->{synchronized(lockA){System.out.println("Thread-1 拿到了 lockA");sleep(100);synchronized(lockB){System.out.println("Thread-1 拿到了 lockB");}}});Threadt2=newThread(()->{synchronized(lockB){System.out.println("Thread-2 拿到了 lockB");sleep(100);synchronized(lockA){System.out.println("Thread-2 拿到了 lockA");}}});t1.start();t2.start();}privatestaticvoidsleep(longmillis){try{Thread.sleep(millis);}catch(InterruptedExceptionignored){}}}

2. 这段代码到底发生了什么?

执行流程非常容易脑补:

  • Thread-1

    • 先拿lockA
    • 再等lockB
  • Thread-2

    • 先拿lockB
    • 再等lockA

结果就是:

  • Thread-1 等 Thread-2 放lockB
  • Thread-2 等 Thread-1 放lockA
  • 双方无限期等待

程序不会崩,也不会报错,但它已经「死」了。

真实业务中,死锁通常长什么样?

死锁在真实项目中,很少像上面这么“工整”,但模式几乎一模一样。

常见场景 1:订单 + 库存

线程 A:锁订单 → 锁库存 线程 B:锁库存 → 锁订单

只要顺序一反,风险立刻出现。

常见场景 2:缓存 + 数据库

  • 一个线程:先查缓存,没命中再锁 DB
  • 另一个线程:先更新 DB,再回写缓存

在高并发下,非常容易卡死。

重点不是你用了几把锁,而是:锁的顺序有没有被统一设计过。

解决方案一:按固定顺序加锁(最重要)

这是成本最低、收益最高的一种方式。

1. 原则很简单

只要多个线程获取多个锁,顺序必须完全一致。

2. 改造上面的死锁代码

publicclassOrderedLockDemo{privatestaticfinalObjectlockA=newObject();privatestaticfinalObjectlockB=newObject();publicstaticvoidmain(String[]args){Threadt1=newThread(()->{synchronized(lockA){System.out.println("Thread-1 拿到了 lockA");sleep(100);synchronized(lockB){System.out.println("Thread-1 拿到了 lockB");}}});Threadt2=newThread(()->{synchronized(lockA){// 注意:顺序统一System.out.println("Thread-2 拿到了 lockA");sleep(100);synchronized(lockB){System.out.println("Thread-2 拿到了 lockB");}}});t1.start();t2.start();}privatestaticvoidsleep(longmillis){try{Thread.sleep(millis);}catch(InterruptedExceptionignored){}}}

3. 为什么这种方式最靠谱?

  • 不依赖 JVM 特性
  • 不依赖并发工具类
  • 不增加复杂度
  • 设计约束消灭死锁

在实际项目中,很多团队会直接把锁顺序写进编码规范

解决方案二:使用 tryLock,避免无限等待

如果你用的是ReentrantLock,那就有了更灵活的手段。

1. 核心思想

拿不到锁,就不要一直等。

2. 示例代码(可运行)

importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.ReentrantLock;publicclassTryLockDemo{privatestaticfinalReentrantLocklockA=newReentrantLock();privatestaticfinalReentrantLocklockB=newReentrantLock();publicstaticvoidmain(String[]args){Threadt1=newThread(()->{try{if(lockA.tryLock(1,TimeUnit.SECONDS)){System.out.println("Thread-1 拿到 lockA");sleep(100);if(lockB.tryLock(1,TimeUnit.SECONDS)){try{System.out.println("Thread-1 拿到 lockB");}finally{lockB.unlock();}}}}catch(InterruptedExceptionignored){}finally{if(lockA.isHeldByCurrentThread()){lockA.unlock();}}});Threadt2=newThread(()->{try{if(lockB.tryLock(1,TimeUnit.SECONDS)){System.out.println("Thread-2 拿到 lockB");sleep(100);if(lockA.tryLock(1,TimeUnit.SECONDS)){try{System.out.println("Thread-2 拿到 lockA");}finally{lockA.unlock();}}}}catch(InterruptedExceptionignored){}finally{if(lockB.isHeldByCurrentThread()){lockB.unlock();}}});t1.start();t2.start();}privatestaticvoidsleep(longmillis){try{Thread.sleep(millis);}catch(InterruptedExceptionignored){}}}

3. tryLock 的真实价值

  • 不会「卡死」
  • 失败可重试
  • 可上报监控
  • 适合非核心路径

但要注意:

  • 代码复杂度会上升
  • 释放锁必须非常小心
  • 更适合中高级并发场景

解决方案三:使用并发包,减少“自己加锁”

很多死锁,本质原因只有一个:

你加了太多不必要的锁。

1. 示例:用 ConcurrentHashMap 替代 synchronized Map

importjava.util.concurrent.ConcurrentHashMap;publicclassConcurrentDemo{privatestaticfinalConcurrentHashMap<String,Integer>map=newConcurrentHashMap<>();publicstaticvoidmain(String[]args){Threadt1=newThread(()->{map.put("A",1);map.computeIfAbsent("B",k->2);});Threadt2=newThread(()->{map.put("B",3);map.computeIfAbsent("A",k->4);});t1.start();t2.start();}}

2. 为什么并发包更安全?

  • 内部锁粒度更细
  • 避免全局大锁
  • 经过大量生产验证

在业务代码中:

  • 能用并发容器,就别手写锁
  • 能用原子类,就别 synchronized

一些非常实用的经验总结

结合实际项目,关于死锁我一般只记这几条:

  1. 死锁不是“偶发 bug”,而是设计缺陷
  2. 多锁场景,第一反应永远是「顺序是否一致」
  3. tryLock 是兜底方案,不是万能解
  4. 并发包不是为了性能,而是为了安全
  5. 看到 synchronized 嵌套,警惕性要拉满

总结

死锁最可怕的地方不在于它难,而在于:

  • 它不会立刻暴露
  • 它只在高并发下出现
  • 它往往发生在你最不想出问题的时候
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 4:18:38

一文说清电感的作用:LC电路中的核心要点

深入理解电感&#xff1a;不只是“阻交流”&#xff0c;更是LC电路的灵魂所在 在电子工程师的日常设计中&#xff0c;电阻、电容和电感被称为三大无源元件。如果说电阻是电路中的“刹车”&#xff0c;电容是“电压缓冲池”&#xff0c;那么 电感就是电流的“惯性轮” ——它不…

作者头像 李华
网站建设 2026/9/3 18:10:43

PyTorch-CUDA-v2.6镜像中启用JIT编译提升推理性能

PyTorch-CUDA-v2.6镜像中启用JIT编译提升推理性能 在现代AI服务部署的实战中&#xff0c;一个常见的挑战是&#xff1a;如何让训练好的PyTorch模型在生产环境中跑得更快、更稳、更轻&#xff1f; 许多团队都经历过这样的窘境——研究阶段模型表现优异&#xff0c;但一旦上线&…

作者头像 李华
网站建设 2026/9/3 18:01:38

CAN总线busoff模拟:vh6501应用详解

模拟CAN总线Bus-Off&#xff1f;用vh6501实现精准故障注入的实战指南你有没有遇到过这样的场景&#xff1a;ECU在实车上莫名其妙“失联”了&#xff0c;诊断报出一连串通信超时&#xff0c;查了半天发现是某个节点进入了Bus-Off状态。可问题是——这个故障太难复现了&#xff0…

作者头像 李华
网站建设 2026/9/9 16:55:26

PyTorch-CUDA-v2.6镜像中使用Git管理机器学习代码的最佳实践

PyTorch-CUDA-v2.6 镜像中使用 Git 管理机器学习代码的最佳实践 在深度学习项目开发中&#xff0c;我们常常会遇到这样的场景&#xff1a;一个模型昨天还能跑出 95% 的准确率&#xff0c;今天却再也复现不了&#xff1b;团队成员提交的代码互相覆盖&#xff0c;导致关键功能丢…

作者头像 李华
网站建设 2026/9/3 5:40:45

通过GNU Radio可视化界面控制SDR硬件通俗解释

用鼠标“画”出一台收音机&#xff1a;GNU Radio 是如何让普通人玩转 SDR 的&#xff1f;你有没有想过&#xff0c;只靠一台几十块钱的 USB 接收器和一个图形界面软件&#xff0c;就能监听 FM 广播、接收飞机ADS-B信号、甚至解码气象卫星图像&#xff1f;这背后的核心技术&…

作者头像 李华
网站建设 2026/9/9 14:41:56

蜂鸣器在工业报警中的应用:核心要点解析

蜂鸣器在工业报警中的应用&#xff1a;从原理到实战的深度拆解你有没有遇到过这样的场景&#xff1f;一台设备突然停机&#xff0c;操作屏上没有任何提示&#xff0c;而旁边的同事却说&#xff1a;“刚才好像听见‘嘀’了一声&#xff1f;”——这正是蜂鸣器在关键时刻发出的求…

作者头像 李华