⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/tengxunmianshiguanxianshangchuxianlesisuonizenmepaichawowoxiedaimazhilianghengaobukeneng-/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

这道题面试官真正想筛什么

「我写代码质量很高,不可能死锁」——这种话面试官最不爱听。死锁不是代码烂的问题,是并发设计的问题。这道题想看三件事:

  1. 知不知道四个必要条件——能成体系答出「互斥 / 请求保持 / 不剥夺 / 循环等待」,比临场拼凑场景的高一个段位。
  2. 真正排查过没有——jstack 输出长什么样、thread -b 的内容、监控里怎么发现,是「实操过」和「背过」的分水岭。
  3. 预防意识——能不能主动把锁排序、tryLock 超时讲清楚,决定能不能进 70 分以上。

只答「两个线程互相等」就只有 30 分——能跑分但解决不了问题。

L1:30 秒答案过基础线

死锁是多个线程互相持有对方需要的锁、又都不释放,导致全部阻塞。线上排查首选 jstack —— 输出会直接告诉你 Found one Java-level deadlock 和具体的代码行号。预防优先用「固定加锁顺序」+「tryLock 超时」两板斧。

经典死锁代码就一段:

public class DeadlockDemo {
private static final Object lockA = new Object();
private static final Object lockB = new Object();

public static void main(String[] args) {
new Thread(() -> {
synchronized (lockA) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lockB) { /* ... */ }
}
}, "Thread-1").start();

new Thread(() -> {
synchronized (lockB) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lockA) { /* ... */ }
}
}, "Thread-2").start();
}
}

Thread-1 拿 lockA 等 lockB,Thread-2 拿 lockB 等 lockA,永远停在那。

L2:2 分钟答案显原理

死锁要发生,必须同时满足四个条件:

上图是死锁产生的四个必要条件:

  • ① 互斥:锁本身的特性,没法破坏(除非不用锁)。
  • ② 请求与保持:持有锁不放,又去要新锁。
  • ③ 不剥夺:自己不释放,外人没法抢。synchronized 就是这样。
  • ④ 循环等待:A 等 B、B 等 A,环就闭合了。

只要破坏其中任意一条,死锁就不会发生。生产里主要打的是 ② 和 ④。

L3:5 分钟答案显生产经验

线上死锁的典型表现:接口响应极慢甚至完全卡死,但 CPU 不高——线程都在 BLOCKED,没人吃 CPU 时间片。

排查首选 jstack

# 1. 找 Java 进程 PID
jps -l
# 12345 com.iocoder.app.Main

# 2. 打线程堆栈
jstack 12345

jstack 会主动帮你找死锁,输出明确:

Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x... (object 0x..., a java.lang.Object),
which is held by "Thread-2"
"Thread-2":
waiting to lock monitor 0x... (object 0x..., a java.lang.Object),
which is held by "Thread-1"

Java stack information for the threads listed above:
"Thread-1":
at DeadlockDemo.lambda$main$0(DeadlockDemo.java:12)
- waiting to lock <0x...>
- locked <0x...>

行号都给你贴出来,直接定位修代码。

复杂场景上 Arthas

阿里开源的 Arthas 是线上诊断神器,不需要重启进程:

java -jar arthas-boot.jar
$ thread -b

thread -b 直接打印「正在阻塞其他线程的线程」——比 jstack 抓全量输出再 grep 干净多了。

监控告警别漏配

每次出问题再上去 jstack 已经晚了。生产环境应该用 ThreadMXBean 做主动检测:

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlocked = threadMXBean.findDeadlockedThreads();
if (deadlocked != null && deadlocked.length > 0) {
ThreadInfo[] infos = threadMXBean.getThreadInfo(deadlocked);
log.error("发现死锁线程:{}", Arrays.toString(infos));
// 接告警平台
}

放到 Spring Boot Actuator 的 HealthIndicator 或定时任务里,5 分钟一次即可。

预防四板斧(按推荐度排)

策略 破坏哪个条件 难度 推荐
固定加锁顺序 循环等待 低 ⭐⭐⭐⭐⭐ 首选
tryLock 超时 不剥夺 中 ⭐⭐⭐⭐ 关键场景必用
缩小同步范围 请求与保持 低 ⭐⭐⭐⭐ 好习惯
避免嵌套锁 请求与保持 看设计 ⭐⭐⭐⭐⭐ 最优解

tryLock 是 ReentrantLock 的杀手锏:

ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();

if (lockA.tryLock(3, TimeUnit.SECONDS)) {
try {
if (lockB.tryLock(3, TimeUnit.SECONDS)) {
try {
// 业务逻辑
} finally { lockB.unlock(); }
} else {
// 超时主动放弃 lockA
}
} finally { lockA.unlock(); }
}

超时拿不到就主动放手,循环等待无从形成。synchronized 做不到这一点,所以多锁场景一律上 ReentrantLock。

直接掉分的几种答法

  • ❌ 「我写代码质量很高,不会死锁」 → 死锁是并发设计问题,不是代码风格问题。这话本身就是减分项。
  • ❌ 「重启服务就行」 → 短期止血可以,但根因没解决,下次还会复现。面试官想看的是定位手段。
  • ❌ 「synchronized 比 ReentrantLock 安全所以不会死锁」 → 反了。synchronized 更容易死锁,因为没有 tryLock 这种主动放弃机制。
  • ❌ 「锁加得越多越安全」 → 锁多了反而更容易嵌套出环。锁的纪律不是「加更多」,是「加更少 + 加得对」。
  • ❌ 「数据库不会有死锁」 → 数据库照样有,InnoDB 自带死锁检测会强行回滚一个事务。Deadlock found when trying to get lock 这报错见过吧?

高频追问怎么接

追问一:死锁和活锁有什么区别?

死锁是 阻塞不动——线程挂在 BLOCKED,CPU 不忙。
活锁是 忙但没进展——两个线程互相让步、互相重试,看起来都在跑,但没人能往前。在 CAS 重试场景里更常见,体现为 CPU 飙高但业务指标停滞。

追问二:synchronized 能像 tryLock 一样超时吗?

不能。这是 ReentrantLock 比 synchronized 多出来的核心能力之一。需要超时、可中断、公平锁、Condition 时,必须上 ReentrantLock。

追问三:线上发现死锁了,先做什么?

三步:① jstack 抓现场(一定要在重启前抓,重启就什么都没了);② 临时降级 / 限流(防止更多请求堆积放大故障);③ 代码 hotfix + 压测复现——千万别只修一处就发布。

追问四:分布式死锁怎么办?

JVM 范畴的 jstack 救不了你——分布式锁(Redis、ZooKeeper)跨进程,靠锁超时 + 监控告警:所有分布式锁强制带 TTL,锁等待超阈值告警。「无超时分布式锁」绝不上生产。

最后说一句

死锁不是写代码不仔细的小问题,是并发设计的系统性问题。能讲清四条件 + 排查链 + 预防策略的候选人,往往都是在线上踩过一回坑学聪明的——「我代码写得好不会死锁」是 30 分,「我有监控加锁排序所以不怕死锁」才是 90 分。

文章目录
  1. 1. 这道题面试官真正想筛什么
  2. 2. L1:30 秒答案过基础线
  3. 3. L2:2 分钟答案显原理
  4. 4. L3:5 分钟答案显生产经验
  5. 5. 直接掉分的几种答法
  6. 6. 高频追问怎么接
  7. 7. 最后说一句