面试题答案
一键面试定位死锁发生的位置和原因
- 使用jstack工具:
- 获取Java进程ID:在Linux系统下可以使用
ps -ef | grep java
命令找到Java进程的PID。在Windows系统下可以通过任务管理器找到Java进程对应的PID。 - 生成线程dump文件:使用
jstack <PID>
命令,例如jstack 1234
(1234为实际的进程ID),该命令会打印出该Java进程中所有线程的堆栈信息。 - 分析线程dump文件:在输出中查找
Deadlock found
相关字样,jstack工具会指出死锁涉及的线程以及它们等待的锁资源,从而定位死锁发生的位置。
- 获取Java进程ID:在Linux系统下可以使用
- 使用VisualVM:
- 安装VisualVM:它是JDK自带的工具,位于
JDK_HOME/bin/jvisualvm.exe
(Windows)或JDK_HOME/bin/jvisualvm
(Linux、Mac)。 - 连接到Java进程:打开VisualVM后,在左侧
Local
下可以找到正在运行的Java进程,选中并右键点击选择Monitor
。 - 检测死锁:在
Monitor
标签页中,点击Thread Dump
按钮获取线程转储信息,VisualVM会自动检测死锁并在Thread
标签页中标注死锁线程。
- 安装VisualVM:它是JDK自带的工具,位于
- 使用JMC(Java Mission Control):
- 启动JMC:位于
JDK_HOME/bin/jmc
。 - 连接到Java进程:在JMC中选择
File
->Connect
,选择要监控的Java进程。 - 分析死锁:在
Threads
视图中,可以查看线程状态,JMC会自动检测死锁并标记出相关线程,通过线程堆栈信息可以确定死锁的位置和原因。
- 启动JMC:位于
设计阶段预防死锁的发生
- 资源分配算法:
- 破坏死锁的四个必要条件:
- 互斥条件:尽量使用资源的共享访问方式,减少独占资源的使用。例如,对于文件操作,可以使用共享锁而非独占锁,在读取文件时允许多个线程同时访问。
- 占有并等待条件:采用资源一次性分配策略,在线程启动前,一次性分配给它所需的所有资源。例如,一个线程需要访问数据库连接和文件资源,在启动该线程前,先获取这两个资源,若获取失败则不启动该线程,避免部分占有资源等待其他资源的情况。
- 不可剥夺条件:设置资源的可剥夺机制,当一个线程占有资源但长时间不释放且其他线程急需该资源时,可强行剥夺该线程的资源。例如,在操作系统中,高优先级线程可以抢占低优先级线程的CPU资源。在Java中,可以通过自定义资源管理类,实现资源的抢占逻辑。
- 循环等待条件:对资源进行排序,规定线程获取资源的顺序必须按照资源的编号顺序来获取。例如,有资源A、B、C,编号分别为1、2、3,所有线程必须先获取编号小的资源,再获取编号大的资源,这样可以避免循环等待。
- 银行家算法:该算法可以用于检测系统是否处于安全状态,在进行资源分配前,先检查分配后系统是否仍然处于安全状态。例如,假设有多个线程竞争多种类型的资源(如CPU、内存、网络带宽等),银行家算法会计算每个线程在获取不同资源后的状态,只有在保证系统不会进入死锁状态的情况下才进行资源分配。
- 破坏死锁的四个必要条件:
- 线程调度策略:
- 避免优先级反转:在多线程系统中,高优先级线程可能会被低优先级线程阻塞,这就是优先级反转。可以采用优先级继承或优先级天花板协议来解决。例如,当高优先级线程等待低优先级线程持有的资源时,低优先级线程的优先级临时提升到与高优先级线程相同,这样低优先级线程会尽快释放资源,避免高优先级线程长时间等待。
- 公平调度:采用公平的线程调度算法,如时间片轮转调度算法,每个线程在相同的时间片内获得CPU执行权,避免某些线程长时间占有资源而导致其他线程饥饿,从而减少死锁的可能性。在Java中,可以通过设置线程的公平性参数(如
ReentrantLock
构造函数中可以设置公平性)来实现一定程度的公平调度。