90%的人答不完整,Java并发面试题:死锁、活锁、饥饿全解析!

软件求生 2025-03-14 09:50:56



大家好,我是活力满满的小米!今天咱们来聊聊 Java 并发编程里一个经典的社招面试题:死锁和活锁的区别,死锁和饥饿的区别!

这道题,十个候选人有八个答不完整,剩下两个答得面试官心态崩溃!为啥呢?因为大家都停留在“概念级别”,但面试官更喜欢听“原理+例子+如何避免”!

今天,我就用一个生动的故事,带你彻底搞懂!

死锁 vs 活锁:两个大侠的恩怨

话说江湖之中,有两位绝世高手——张无忌和杨过。他们武艺超群,却因一件神器发生了争执。这件神器就是“并发编程的终极宝典”,只有同时得到“线程 A 之钥”和“线程 B 之钥”才能开启。

有一天,他们同时冲向钥匙!

张无忌:先拿到了线程 A 之钥,然后伸手去拿线程 B 之钥。

杨过:先拿到了线程 B 之钥,然后伸手去拿线程 A 之钥。

结果,两人站在门口,谁也不肯放手,谁也拿不到完整的钥匙。两人僵持不下,整个武林大会因此停滞。

这,就是死锁(Deadlock)!

死锁的特点:

互斥(每个线程只能独占一个资源)。

持有并等待(拿到一部分资源后,还要等另一部分)。

不可抢占(资源不能被强制释放)。

循环等待(线程 A 等线程 B,线程 B 又等线程 A)。

那么,什么是活锁?

这时候,郭靖路过,看到了两位大侠的困境,于是灵机一动,大声说道:

“你们互相谦让一下不就好了?”

张无忌和杨过一听,纷纷松开手,示意对方先拿。

但问题是——他们都不愿意先拿,一直谦让,结果还是谁也拿不到钥匙!

这,就是活锁(Livelock)!

活锁的特点:

线程并没有真正停滞,而是一直在尝试解决问题,但永远无法前进。

线程之间会相互让步,导致任务一直得不到执行。

经典案例:两个线程不断重试获取锁,并在失败时让步,导致一直卡在让步阶段。

死锁 vs 饥饿:武林菜馆的故事

在武林大会旁边,有一家很火爆的菜馆,客人络绎不绝。

这家店有三类客人:

江湖豪侠(线程 A):身手敏捷,点菜快,吃饭快,每次来都能马上吃到饭。

普通武者(线程 B):点菜后稍微等一会儿,但最终还是能吃上。

乞丐(线程 C):排队很久,但永远吃不到饭……

1. 死锁的情况

这家店只有一张大桌子,但规则是:

必须等所有人都到齐,才能上菜(类似于必须获取所有锁才能执行)。

如果有人迟到,所有人都要等。

某天,五个客人同时来吃饭,四个人都坐下了,最后一个人迟迟不来,结果——大家都等着他,谁也吃不到饭!

这,就是死锁!

2. 饥饿的情况

乞丐(线程 C)发现自己每次都吃不上饭,为什么呢?

因为每次他快要排到时,都会被插队(高优先级的线程不断执行)。

结果就是,他永远得不到 CPU 时间片,只能一直等。

这,就是饥饿(Starvation)!

饥饿的特点:

低优先级的线程长期得不到 CPU 资源,导致无法执行。

可能是由于高优先级线程过于频繁地抢占CPU 时间片。

常见于线程池、锁竞争、数据库资源争用等场景。

避免死锁的常见方法

1、避免嵌套锁:减少线程交叉获取多个锁的情况,比如永远按照固定顺序获取锁,避免循环等待。

2、使用超时获取锁:如果获取不到锁,不要一直等,而是放弃或重试。

3、采用死锁检测:JVM 的线程 dump可以查看死锁情况,工具如 jconsole、VisualVM。

解决活锁的方法

1、随机退让:不要固定策略,而是加入随机性,让线程在冲突时随机等待一段时间。

2、限制重试次数:如果多次失败,则终止或降级处理,而不是无限循环重试。

解决饥饿的方法

1、公平锁:使用 ReentrantLock(true) 来保证线程按顺序执行,不会让低优先级线程长期饿死。

2、限制高优先级线程的占用:比如在线程池中,不让某些高优先级任务无限制地执行。

总结

END

面试时,回答这类问题不要只背概念,带上案例+代码+解决方案,直接让面试官眼前一亮!

今天的分享就到这里,如果你学会了,记得点个“赞”!如果你觉得有用,就转发给正在备战 Java 面试的朋友吧!

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!

0 阅读:0

软件求生

简介:从事软件开发,分享“技术”、“运营”、“产品”等。