面试题答案
一键面试- 使用jps定位进程ID:
- 在命令行中执行
jps
命令,它会列出当前运行的Java进程及其对应的进程ID。例如,如果我们的Spring Boot应用程序是一个独立的Java进程,它会在列表中显示,通过进程名称找到对应的进程ID。
- 在命令行中执行
- 使用jstack获取线程堆栈信息:
- 得到进程ID后,执行
jstack <进程ID>
命令。这会输出该Java进程中所有线程的堆栈跟踪信息。 - jstack输出的信息中,会包含每个线程的状态(如RUNNABLE、BLOCKED等)以及线程持有和等待的锁信息。
- 查找状态为
BLOCKED
的线程,这些线程很可能正在等待某个锁,而死锁往往涉及到多个BLOCKED
状态的线程相互等待。
- 得到进程ID后,执行
- 分析线程堆栈信息:
- 在jstack输出中,重点关注
java.util.concurrent.locks.ReentrantLock
(如果使用了这种锁)以及其他自定义同步锁相关的信息。 - 例如,对于
ReentrantLock
,会显示锁的持有者和等待者。如果发现多个线程相互等待对方持有的锁,就可能存在死锁。 - 同时,查看线程执行的代码位置,通常会显示线程正在执行的方法和类名,通过这些信息可以追溯到代码中具体的同步块。
- 在jstack输出中,重点关注
- 使用VisualVM(可选但推荐):
- 启动VisualVM,它是JDK自带的可视化工具。
- 在VisualVM中,找到我们的Spring Boot应用程序进程。
- 进入“线程”标签页,这里可以直观地看到线程的状态和活动情况。
- 点击“线程dump”按钮,可以获取类似jstack输出的线程堆栈信息,并且以更友好的可视化方式展示,方便分析。
- 代码审查:
- 根据工具分析出的可能存在死锁的代码位置,对相关代码进行审查。
- 检查同步块的嵌套情况,是否存在不合理的锁嵌套顺序。例如,如果线程A先获取锁L1再获取锁L2,而线程B先获取锁L2再获取锁L1,就容易导致死锁。
- 检查锁的获取和释放逻辑,确保所有获取的锁都能正确释放,避免因锁未释放导致其他线程等待。
- 解决死锁问题:
- 调整锁的顺序:统一所有线程获取锁的顺序,避免交叉获取锁。例如,所有线程都先获取锁L1,再获取锁L2。
- 使用定时锁:在获取锁时使用
tryLock(long timeout, TimeUnit unit)
方法,设置一个超时时间。如果在规定时间内无法获取锁,线程可以放弃并执行其他操作,避免无限等待。 - 减少锁的粒度:尽量缩小同步块的范围,只在必要的代码段加锁,减少线程竞争锁的时间和机会。
在整个排查和解决过程中,每次修改代码后,需要进行充分的测试,确保死锁问题得到解决且没有引入新的问题。