在线 Java 面试刷题已更新239题图文并茂https://www.quanxiaoha.com/java-interview面试考察点概念理解面试官不仅仅是想知道 两个线程互相等对方释放锁 这句话更是想看你能不能说清楚死锁产生的四个必要条件。能说出这四个条件说明你的理解是成体系的不是碎片化的。排查能力线上出了死锁你怎么定位能不能熟练使用jstack、jconsole、Arthas这些工具这块区分度很大实际排查过死锁的候选人回答起来明显更有底气。预防意识考察你是否掌握了死锁的预防策略以及在日常编码中是否有意识地去避免死锁。能主动提及锁排序、超时机制这些方案的候选人面试官会给加分。核心答案死锁是指两个或多个线程互相持有对方需要的锁又都不肯释放自己手中的锁导致所有线程永远阻塞的现象。先来看一个最经典的死锁代码public class DeadlockDemo { privatestaticfinal Object lockA new Object(); privatestaticfinal Object lockB new Object(); public static void main(String[] args) { // 线程 1先拿 lockA再请求 lockB new Thread(() - { synchronized (lockA) { System.out.println(Thread-1 持有 lockA等待 lockB); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lockB) { System.out.println(Thread-1 获得锁); } } }, Thread-1).start(); // 线程 2先拿 lockB再请求 lockA new Thread(() - { synchronized (lockB) { System.out.println(Thread-2 持有 lockB等待 lockA); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lockA) { System.out.println(Thread-2 获得锁); } } }, Thread-2).start(); } }运行后程序永远不会结束——Thread-1 拿着 lockA 等 lockBThread-2 拿着 lockB 等 lockA谁也动不了。深度解析一、死锁产生的四个必要条件死锁不是随便就能发生的必须同时满足以下四个条件img上图展示了死锁产生的四个必要条件。逐个解释① 互斥条件锁本身就是互斥的同一时刻只能被一个线程持有。这个条件是锁的基本特性基本没法破坏除非不用锁。② 请求与保持线程已经持有至少一把锁还在请求另一把锁且不释放已经持有的锁。上面的代码就是典型的 请求与保持——Thread-1 拿着 lockA 不放还去请求 lockB。③ 不剥夺条件线程已经获得的锁不能被其他线程强制抢占只能由持有者自己释放。synchronized就是这样拿到锁后别人没法让你交出来。④ 循环等待多个线程之间形成环形等待链——A 等 B 的锁B 等 A 的锁。这是死锁最直观的表现。关键点四个条件必须同时成立才会死锁只要破坏其中任意一个死锁就能被打破。实际开发中我们主要破坏 ② 和 ④。加入小哈的星球你将获得:专属的项目实战4个项目 / 1v1 提问 / 简历修改 /Java 学习路线 /社群讨论 /学习打卡 / 每月赠书《仿小红书微服务架构》 已完结基于 Spring Cloud Alibaba Spring Boot 3.x JDK 17..., 点击查看项目介绍演示地址http://116.62.199.48:7070/《Spring AI 应用RAG 智能客服》已完结, 基于 Spring AI Spring Boot 3.x JDK 21《秒杀系统设计》正在更新中单体到微服务高并发架构演进《前后端分离博客项目全栈开发》已完结,演示链接http://116.62.199.48/项目阅读地址https://quanxiaoha.com/column截止目前累计输出 120w 字讲解图 4013 张还在持续爆肝中..戳我加入学习解锁全部项目已有4500小伙伴加入二、如何排查死锁线上系统出现死锁通常的表现是某些接口响应极慢甚至完全卡死CPU 使用率不高因为线程都阻塞了线程 dump 中能看到大量的BLOCKED状态线程。方法一jstack命令最常用# 1. 先找到 Java 进程 PID jps -l # 输出12345 DeadlockDemo # 2. 用 jstack 打印线程堆栈 jstack 12345输出中会明确告诉你发现死锁Found one Java-level deadlock: Thread-1: waiting to lock monitor 0x0000... (object 0x0000..., a java.lang.Object), which is held by Thread-2 Thread-2: waiting to lock monitor 0x0000... (object 0x0000..., 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 0x0000... (a java.lang.Object) - locked 0x0000... (a java.lang.Object) Thread-2: at DeadlockDemo.lambda$main$1(DeadlockDemo.java:22) - waiting to lock 0x0000... (a java.lang.Object) - locked 0x0000... (a java.lang.Object)jstack会直接帮你找到死锁的线程和具体的代码行号非常直观。方法二jconsole/VisualVM图形化如果你能连到服务器或者本地开发环境用jconsole或VisualVM打开在 线程 面板点击 检测死锁 按钮就能看到哪些线程发生了死锁。图形化界面比命令行更直观适合本地调试。方法三Arthas线上诊断利器# 安装并启动 Arthas java -jar arthas-boot.jar # 查看线程死锁 thread -bthread -b会直接打印阻塞其他线程的线程信息包括持有哪些锁、阻塞了谁。阿里开源的 Arthas 在线上问题排查中真的太好用了强烈推荐掌握。方法四代码层面检测// ThreadMXBean 可以编程式检测死锁 ThreadMXBean threadMXBean ManagementFactory.getThreadMXBean(); long[] deadlockedThreads threadMXBean.findDeadlockedThreads(); if (deadlockedThreads ! null) { ThreadInfo[] threadInfos threadMXBean.getThreadInfo(deadlockedThreads); // 记录日志、告警 }可以在监控系统中定时检测发现死锁自动告警。三、如何解决和预防死锁上面说了破坏四个必要条件中的任意一个就能打破死锁。实际开发中有以下几种常用策略策略一固定加锁顺序破坏循环等待这是最简单也最有效的方式——所有线程都按相同的顺序获取锁。// 修复后的代码两个线程都先 lockA 再 lockB public static void main(String[] args) { // 线程 1先 lockA再 lockB new Thread(() - { synchronized (lockA) { synchronized (lockB) { // 业务逻辑 } } }).start(); // 线程 2也是先 lockA再 lockB顺序一致 new Thread(() - { synchronized (lockA) { synchronized (lockB) { // 业务逻辑 } } }).start(); }两个线程都先请求 lockA 再请求 lockB不会出现 一个先 A 后 B、另一个先 B 后 A 的交叉等待循环等待条件被打破。实际开发中可以按锁对象的hashCode值大小来确定加锁顺序。策略二使用tryLock超时机制破坏不剥夺条件ReentrantLock lockA new ReentrantLock(); ReentrantLock lockB new ReentrantLock(); try { // 尝试获取 lockA最多等 3 秒 if (lockA.tryLock(3, TimeUnit.SECONDS)) { try { // 获取到 lockA 后尝试获取 lockB if (lockB.tryLock(3, TimeUnit.SECONDS)) { try { // 两把锁都拿到了执行业务逻辑 } finally { lockB.unlock(); } } else { // 获取 lockB 超时主动放弃 lockA System.out.println(获取 lockB 超时放弃操作); } } finally { lockA.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); }tryLock在指定时间内获取不到锁就返回false主动放弃已经持有的锁。这就打破了 不剥夺条件——锁虽然没有被外部强制抢占但线程自己超时放弃了效果一样。synchronized做不到这一点这也是ReentrantLock的一大优势。策略三减小锁粒度缩小同步范围锁的代码块越大持有锁的时间越长死锁概率就越高。尽量把不需要同步的操作移到同步块外面// 不好整个方法都加锁持有锁时间太长 synchronized (lock) { prepareData(); // 不需要锁 updateShared(); // 需要锁 sendNotify(); // 不需要锁 } // 好只锁必要的部分 prepareData(); synchronized (lock) { updateShared(); } sendNotify();策略四避免嵌套锁能不用嵌套锁就不用。如果必须在一个锁里请求另一个锁说明设计可能有问题考虑重构一下逻辑把嵌套锁拆成独立的锁操作。四、四种策略对比策略破坏哪个条件实现难度推荐程度固定加锁顺序循环等待简单✅首选tryLock超时不剥夺条件中等✅ 推荐减小锁粒度请求与保持简单✅ 好习惯避免嵌套锁请求与保持需设计✅ 最佳实践生产环境最常用的就是固定加锁顺序tryLock超时的组合拳基本上能把死锁风险降到很低。面试高频追问死锁和活锁有什么区别死锁是线程阻塞不动了活锁是线程没阻塞但在不停地重试失败比如两个线程互相让步都拿不到锁本质上是 忙但没进展。数据库也会有死锁吗怎么处理会。数据库的死锁检测更成熟——MySQL InnoDB 会自动检测死锁并回滚其中一个事务。应用层也可以通过tryLock超时来避免。synchronized能检测死锁吗synchronized本身不能但可以通过jstack、ThreadMXBean等外部工具来检测。ReentrantLock的tryLock则可以从编码层面主动避免死锁。常见面试变体写一个死锁的代码示例线上出了死锁你怎么排查如何预防死锁synchronized和ReentrantLock哪个更容易出现死锁记忆口诀死锁四条件互斥、请求保持、不剥夺、循环等待。破坏任意一个死锁不成立。预防三板斧锁排序固定顺序、锁超时tryLock、锁最小化缩小范围。总结面试答死锁三层递进先说清楚什么是死锁和四个必要条件再讲排查手段jstack、Arthas最后重点展开预防策略固定加锁顺序 tryLock超时。如果能在回答中穿插一段自己线上排查死锁的经历面试官基本就稳了。加入小哈的星球你将获得:专属的项目实战4个项目 / 1v1 提问 / 简历修改 /Java 学习路线 /社群讨论 /学习打卡 / 每月赠书《仿小红书微服务架构》 已完结基于 Spring Cloud Alibaba Spring Boot 3.x JDK 17..., 点击查看项目介绍演示地址http://116.62.199.48:7070/《Spring AI 应用RAG 智能客服》已完结, 基于 Spring AI Spring Boot 3.x JDK 21《秒杀系统设计》正在更新中单体到微服务高并发架构演进《前后端分离博客项目全栈开发》已完结,演示链接http://116.62.199.48/项目阅读地址https://quanxiaoha.com/column截止目前累计输出 120w 字讲解图 4013 张还在持续爆肝中..戳我加入学习解锁全部项目已有4500小伙伴加入1. 我的私密学习小圈子从0到1手撸企业实战项目~ 2. 美团二面线程池队列满了怎么办不能拒绝我沉默了... 3. 面试官什么是守护线程和普通线程有什么区别 4. 只改了五行代码接口吞吐量提升了10多倍最近面试BAT整理一份面试资料《Java面试BATJ通关手册》覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。 获取方式点“在看”关注公众号并回复 Java 领取更多内容陆续奉上。PS因公众号平台更改了推送规则如果不想错过内容记得读完点一下“在看”加个“星标”这样每次新文章推送才会第一时间出现在你的订阅列表里。 点“在看”支持小哈呀谢谢啦