图片右键另存为查看详细。
什么情况下Java程序会产生死锁?如何定位、修复?
死锁是一种特定的程序状态,在实体之间,由于循环依赖导致彼此一直处于等待之中,没有任何个体可以继续前进。死锁不仅仅是在线程之间会发生,存在资源独占的进程之间同样也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。
我们可以通过jstack或者jconsle来定位问题。
死锁产生的原因
1.竞争资源
系统中供多个进程共享的资源其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。
2.进程间推进顺序非法
进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。
死锁的四个必要条件
-
互斥条件
一段时间内某资源只由一个进程占用。如果此时还有其它进程请求该资源,则请求者只能等待,直至占有该资源的进程用毕释放。 -
请求和保持条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。 -
不剥夺条件
指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。 -
环路等待条件
若干进程间形成首尾相接循环等待资源的关系。
死锁如何避免
1.尽量避免使用多个锁
2.计好锁的获取顺序
- 将对象(方法)和锁之间的关系,用图形化的方式表示分别抽取出来
- 然后根据对象之间组合、调用的关系对比和组合,考虑可能调用时序
- 按照可能时序合并,发现可能死锁的场景
3.使用带超时的方法,为程序带来更多可控性
- Object.wait(…)或者CountDownLatch.await(…)指定超时时间
- .ReentrantLock trylock使用时候。我们希望条件允许就尝试插队,不然就按照现有公平性规则等待。一般采用如下方法:
if (lock.tryLock() || lock.tryLock(timeout, unit)) {
// ...
}
4.静态代码分析(如FindBugs)去查找固定的模式,进而定位可能的死锁或者竞争情况
死锁检测工具
死锁demo
public class DeadLockDemo {
private static String A = "A";
private static String B = "B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
private void deadLock() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (A) {
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("1");
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (B) {
synchronized (A) {
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
}
}
jstack
JDK自带的命令行工具,线程Dump分析。
- Jps来查看java进程id
- jstack id名
JConsole
JDK自带的图形化界面工具。
JConsole使用的就是ThreadMXBean获取死锁线程并分组,然后打印相关线程信息。
具体使用
https://blog.csdn.net/abc86319253/article/details/49534225
线程进入了死循环,导致其他线程一直等待,这种问题如何诊断?
循环死锁,会导致cpu某线程的cpu时间片占用率相当高。
Linux上,可以使用top命令配合grep Java之类,找到忙的pid;然后,转换成16进制,就是jstack输出中的格式;再定位代码。
备注:
jps -l 查看java进程