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

【面朝大厂】面试官:手写一个必然死锁的例子

1. 死锁概念

死锁是指两个或多个线程在互相等待对方释放资源,导致所有线程都无法继续执行的状态。在并发编程中,死锁是一种常见的风险,尤其当多个线程以不同顺序请求共享资源时。

2. 死锁的四个必要条件(Coffman条件)

  • 互斥:资源一次只能被一个线程占用。

  • 保持并等待:线程在持有资源的同时,等待获取其他资源。

  • 非剥夺:线程已获得的资源只能由自己释放,不能被其他线程强行剥夺。

  • 循环等待:存在一组线程,每个线程都在等待下一个线程所持有的资源,形成循环。

这四个条件必须同时满足才会发生死锁。因此,打破任意一个条件即可预防或避免死锁。

3. 必然死锁的例子(Java代码)

下面是一个经典的死锁示例:两个线程Thread1Thread2分别需要锁lockAlockB,但获取顺序相反,导致相互等待。

java

public class DeadlockExample { // 定义两个资源(锁对象) private static final Object lockA = new Object(); private static final Object lockB = new Object(); public static void main(String[] args) { // 线程1:先锁A,再锁B Thread thread1 = new Thread(() -> { synchronized (lockA) { System.out.println("Thread1 acquired lockA"); try { // 模拟一些工作,增加死锁概率 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread1 waiting for lockB..."); synchronized (lockB) { System.out.println("Thread1 acquired lockB"); } } }); // 线程2:先锁B,再锁A Thread thread2 = new Thread(() -> { synchronized (lockB) { System.out.println("Thread2 acquired lockB"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread2 waiting for lockA..."); synchronized (lockA) { System.out.println("Thread2 acquired lockA"); } } }); thread1.start(); thread2.start(); // 等待线程结束(实际上由于死锁,程序不会正常结束) try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("程序结束"); } }

运行结果分析

  • Thread1持有lockAThread2持有lockB后,各自等待对方持有的锁,导致死锁。

  • 程序永远不会打印“程序结束”,因为两个线程永久阻塞。

4. 为什么这个例子必然死锁?

  • 互斥synchronized保证了锁的互斥性。

  • 保持并等待:每个线程在持有自己第一个锁后,并没有释放,而是继续请求第二个锁。

  • 非剥夺:Java 的synchronized不支持外部强制释放锁,只能由线程自己退出同步块释放。

  • 循环等待Thread1等待Thread2持有的lockBThread2等待Thread1持有的lockA,形成循环。

由于代码执行顺序可能不同,但通过Thread.sleep(100)人为增大了两个线程同时持有第一个锁并等待第二个锁的概率,在大多数运行环境下必然导致死锁。

5. 如何检测死锁?

  • jstack 工具:运行jstack <pid>查看线程堆栈,会显示死锁信息,例如:

    text

    Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00007f8b7c004b18 (object 0x000000076b5b6d50, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x00007f8b7c006b18 (object 0x000000076b5b6d60, a java.lang.Object), which is held by "Thread-1"
  • VisualVM等图形化工具也可以检测死锁。

  • 代码中动态检测:可以使用ThreadMXBeanfindDeadlockedThreads()方法。

6. 如何避免死锁?

6.1 锁顺序(破坏循环等待)

让所有线程以相同的顺序获取锁。例如,规定总是先锁lockA再锁lockB

java

// 线程1和线程2都按 lockA -> lockB 顺序 synchronized (lockA) { synchronized (lockB) { // ... } }
6.2 使用显式锁的tryLock超时(破坏保持并等待)

使用java.util.concurrent.locks.ReentrantLocktryLock(long timeout, TimeUnit unit)方法,在指定时间内获取不到锁则释放已持有的锁并重试,从而避免无限等待。

java

ReentrantLock lockA = new ReentrantLock(); ReentrantLock lockB = new ReentrantLock(); // 线程1 boolean gotLockA = lockA.tryLock(1, TimeUnit.SECONDS); if (gotLockA) { try { boolean gotLockB = lockB.tryLock(1, TimeUnit.SECONDS); if (gotLockB) { try { // 业务逻辑 } finally { lockB.unlock(); } } else { // 没拿到lockB,释放lockA并重试或放弃 } } finally { lockA.unlock(); } }
6.3 使用并发工具类

利用java.util.concurrent包提供的更高级别工具,如CountDownLatchCyclicBarrierSemaphore等,它们内部实现了资源管理,可以避免手动锁带来的死锁风险。

6.4 避免在持有一个锁时调用外部方法

如果必须在持有锁时调用外部方法,要确保该方法不会反过来请求本线程持有的锁,或者使用开放调用(即不加锁调用),减少死锁可能。

6.5 死锁检测与恢复

允许死锁发生,但通过检测机制发现后,采取干预措施(如强制中断某个线程或释放资源)。但这种方式实现复杂且开销大,较少使用。

7. 扩展:活锁与饥饿

  • 活锁:线程虽然没有被阻塞,但不断重复相同的操作,却始终无法进展(例如两个线程互相谦让资源,导致谁也无法获取全部资源)。与死锁的区别是线程状态是运行中,但无法继续执行。

  • 饥饿:一个或多个线程因为优先级太低或资源分配不公平,始终无法获得所需资源,导致无法执行。

8. 总结

手写死锁例子是面试中常见的题目,核心是理解死锁的四个必要条件。通过上述例子,我们展示了最简单的死锁场景,并介绍了检测和避免死锁的方法。在实际开发中,遵循锁顺序、使用超时锁、尽量使用高级并发工具,可以有效避免死锁问题。


附录:更多变种死锁示例

动态锁顺序死锁

有时锁顺序取决于参数,也可能导致死锁:

java

public void transferMoney(Account from, Account to, int amount) { synchronized (from) { synchronized (to) { // 转账逻辑 } } }

如果两个线程同时调用transferMoney(a, b, 10)transferMoney(b, a, 20),可能发生死锁。解决方法是对账户进行排序,总是按固定顺序加锁(例如使用System.identityHashCode排序)。

协作对象间的死锁

在多个对象互相调用时,也可能发生死锁。例如,一个线程调用A.methodA持有锁A,内部调用B.methodB需要锁B;另一个线程相反。

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

相关文章:

  • 绝对路径 vs 相对路径
  • 赛微思咨询案例效果好吗,能帮江浙沪企业解决发展难题吗? - 工业品牌热点
  • 【日记】第二次把衣服洗坏了……(1649 字)
  • 彻底服了:HashMap 夺命二十一问,顶不住了!
  • 湖南讯灵AI服务质量好不好,长沙选购它的费用大概多少钱? - mypinpai
  • ssh 配置公钥登录,关闭密码登录
  • 【雷达导弹导引头原理】 第二章 (一)防空导弹系统战术技术特性
  • 说说江苏地区专注半导体陶瓷制造的企业,哪家性价比高 - 工业品牌热点
  • 本科vs硕士毕业论文AIGC检测标准对比:差距到底有多大?
  • 亿级流量架构之服务降级思路与方法
  • 比话降AI使用教程:硕士论文降AIGC率专业指南
  • 多种改进机械臂算法仿真、DDPG强化学习+自适应扰动RBF、深度学习+3自由度+2自由度附Matlab代码
  • 毕业论文AIGC率超标不要慌!这份攻略帮你稳过检测
  • 聊聊紧凑绘图仪的报价情况,哪家性价比更高值得推荐? - 工业推荐榜
  • 【雷达导弹导引头原理】 第一章 全球地缘政治经济威胁分析
  • 拉拉裤堆垛机的优质生产商推荐,福建地区选哪家比较好 - myqiye
  • MySQL 8.0 可以操作 JSON 了,牛逼。。。
  • OpenClaw爆火60天:中国产业AI落地的“又一次集体进化”
  • 面朝大厂:ConcurrentHashMap 面试题全方位深度解析
  • 2026年城市SUV推荐:15万左右预算家庭通勤评测,解决油耗与空间痛点并附排名 - 品牌推荐
  • T型三电平逆变器、最小开关损耗调制(DPWM)载波生成+减小逆变器开关损耗仿真
  • 2026年2月乙醇市场观察:优质乙醇厂家排行揭秘,工业乙醇/回收废乙醇/乙醇/回收酒精/食用酒精,乙醇企业推荐排行榜单 - 品牌推荐师
  • 改稿速度拉满!AI论文工具 千笔·专业论文写作工具 VS 知文AI,专为本科生打造
  • 2026年城市SUV推荐:基于多场景实测评价,针对续航焦虑与可靠性痛点精准指南 - 品牌推荐
  • 自动化测试小心四大陷阱!
  • 融合随机开关频率与低共模电压矢量的永磁同步电机模型预测控制研究
  • Windows系统修改文件创建时间,修改时间的三个方法
  • 2026年3月防腐涂料厂家推荐:行业测评与长效防护选择指南 - 品牌鉴赏师
  • 271_尚硅谷_管道快速入门案例
  • 学霸同款!口碑爆棚的降AI率平台 —— 千笔·专业降AI率智能体