卡码笔记-最强八股文
首页
计算机基础
C++
Java
Go
🔥大模型🔥
大厂面经 大厂面经
大模型面经
Java面经
C++面经
简历专栏
代码随想录
(opens new window)
首页
计算机基础
C++
Java
Go
🔥大模型🔥
大厂面经 大厂面经
大模型面经
Java面经
C++面经
简历专栏
代码随想录
(opens new window) 基础与面向对象
集合
异常
字符串
JVM
并发与多线程
Java创建线程有几种方式?线程start和run的区别?Java线程安全的实现?synchronized和lock、reentrantlock的区别是什么?说一说你对synchronized的理解?volatile关键字的作用有哪些?volatile与synchronized的对比?你知道Java中有哪些锁吗?为什么要有线程池,线程太多会怎样?说一说线程池的常用参数BIO、NIO、AIO的区别什么是死锁?如何避免和排查简要回答详细回答代码示例排查步骤知识图解知识扩展JDK
Spring
设计模式
# 什么是死锁?如何避免和排查死锁? # 简要回答 死锁是指多个线程在竞争多个资源时,互相持有对方需要的资源,导致所有线程都无法继续执行的永久阻塞状态。 发生死锁通常需要同时满足四个条件:互斥条件、持有并等待条件、不可剥夺条件、环路等待条件。 避免死锁的核心思路是破坏这四个条件中的任意一个,工程上最常见的方法是:统一加锁顺序、减少嵌套锁、使用tryLock超时机制、不要在锁内执行耗时操作。 排查死锁时可以通过jps + jstack、jcmd Thread.print -l、jconsole/arthas查看线程栈,重点关注BLOCKED线程、锁持有关系以及输出中的Found one Java-level deadlock。 # 详细回答 什么是死锁 死锁本质上是循环等待。线程A持有资源1等待资源2,线程B持有资源2等待资源1,双方都不释放自己已经拿到的资源,最终谁也执行不下去。 在Java里,死锁不仅可能发生在synchronized之间,也可能发生在ReentrantLock、数据库锁、分布式锁以及多种锁组合使用的场景中。 死锁产生的四个必要条件 互斥条件:同一时刻一个资源只能被一个线程占有。 持有并等待条件:线程已经持有一个资源,同时继续申请其他资源。 不可剥夺条件:线程已经拿到的资源,在自己释放前不能被强行抢走。 环路等待条件:多个线程之间形成首尾相接的资源等待环。 只有这四个条件同时成立时,才会形成死锁,所以只要破坏其中任意一个条件,就能避免死锁。 Java中常见的死锁场景 加锁顺序不一致:线程A先锁A再锁B,线程B先锁B再锁A,是最常见的死锁原因。 多资源混合加锁:本地锁、数据库行锁、分布式锁混用,如果获取顺序不统一,容易出现循环等待。 锁内执行耗时操作:在线程持锁期间执行RPC、数据库查询、文件IO,会放大锁竞争时间,增加死锁概率。 线程池任务互相等待:固定大小线程池中的任务互相Future.get()等待,也可能形成“线程池死锁”或饥饿型死锁。 如何避免死锁 统一资源申请顺序:这是最常用的方法。比如所有线程都先锁A再锁B,就不会形成环路等待。 一次性申请所有资源:如果某个资源拿不到,就释放已经获取的资源后重试,避免“持有并等待”。 使用可超时锁:ReentrantLock.tryLock(timeout)可以让线程在等待一段时间后放弃,避免永久阻塞。 使用可中断锁:lockInterruptibly()允许线程在等待锁时响应中断,适合复杂协作场景。 缩小锁粒度,减少嵌套锁:只锁真正需要同步的代码,尽量不要在一个锁里再套另一个锁。 不要在锁内做耗时操作:例如网络调用、长事务、远程接口、复杂计算等,都会显著增加死锁风险。 优先使用并发工具类:能用ConcurrentHashMap、原子类、阻塞队列解决的问题,就尽量不要手写多把锁配合。 # 代码示例 public class DeadLockDemo {
private static final Object LOCK_A = new Object();
private static final Object LOCK_B = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (LOCK_A) {
sleep(100);
synchronized (LOCK_B) {
System.out.println("t1 done");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (LOCK_B) {
sleep(100);
synchronized (LOCK_A) {
System.out.println("t2 done");
}
}
}, "t2");
t1.start();
t2.start();
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
1234567891011121314151617181920212223242526272829303132333435上面代码中,t1先拿LOCK_A再等LOCK_B,t2先拿LOCK_B再等LOCK_A,满足死锁四个条件后就会永久卡住。 如果想避免这类问题,可以让所有线程都按照固定顺序获取锁,例如统一先拿LOCK_A再拿LOCK_B。 # 排查步骤 先看现象 接口长时间无响应、线程一直不结束、CPU不一定很高,但业务明显“卡住”。 线程状态中经常能看到多个线程长期处于BLOCKED或等待锁的状态。 定位Java进程 使用jps -l查看Java进程ID。 导出线程栈 使用jstack
# 知识扩展 扩展: jstack典型会打印出线程的等待关系,例如某个线程“waiting to lock”某个对象,而这个对象又被另一个线程持有。 如果输出中出现Found one Java-level deadlock,说明JVM已经检测到Java层面的循环等待,这也是排查死锁时最直接的证据。 在业务代码中还可以使用ThreadMXBean#findDeadlockedThreads()做定时探测,适合接入监控系统做告警。 面试官可能追问: Q1:synchronized和ReentrantLock都可能发生死锁吗?
都会。只要线程之间形成循环等待,就可能死锁。区别在于ReentrantLock支持tryLock()、超时和可中断等待,因此更容易做死锁预防。 Q2:死锁、活锁、饥饿有什么区别?
死锁:线程彼此等待,完全不再向前推进。 活锁:线程没有阻塞,一直在重试或让步,但是业务仍然没有进展。 饥饿:某个线程长期抢不到资源,其他线程还能继续执行。 Q3:线程池也会发生死锁吗?
会。例如固定大小线程池中,任务A等待任务B结果,任务B又等待任务A或者等待线程池中尚未执行的任务,就可能出现线程池内部互相等待的问题。 Last Updated: 5/25/2026, 3:50:35 PM
←
BIO、NIO、AIO的区别
JDK8有什么新特性?
→
评论 验证登录状态...
侧边栏 夜间 卡码简历 代码随想录 卡码投递表🔥 2026群 添加客服微信
PS:通过微信后,请发送姓名-学校-年级-2026实习/校招 支持卡码笔记 鼓励/支持/赞赏Carl 1.
如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪