java面试题-什么情况下导致线程死锁,遇到线程死锁该怎么解决?
1. 什么是线程死锁?
线程死锁是指多个线程因竞争资源而造成的一种僵局,互相等待对方释放资源,导致所有线程无法向前推进。死锁产生的必要条件包括互斥条件、不剥夺条件、请求和保持条件以及循环等待条件。
2. 死锁的必要条件
2.1 互斥条件
线程对资源的访问是排他性的,某一时刻只能有一个线程占有资源。
2.2 不剥夺条件
线程获得的资源在未使用完毕之前,不能被其他线程强行夺走,只能由获得资源的线程自己释放。
2.3 请求和保持条件
线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有。
2.4 循环等待条件
存在一个线程资源的循环等待链,每个线程已获得的资源同时被链中下一个线程所请求。
3. 线程死锁的案例代码
考虑以下简单的死锁案例代码:
Runnable r1 = () -> {
synchronized ("A") {
// 拿到A锁
System.out.println(Thread.currentThread().getName() + "拿到A锁");
synchronized ("B") {
// 拿到A锁和B锁
System.out.println(Thread.currentThread().getName() + "拿到A锁和B锁");
}
}
};
Runnable r2 = () -> {
synchronized ("B") {
// 拿到B锁
System.out.println(Thread.currentThread().getName() + "拿到B锁");
synchronized ("A") {
// 拿到A锁和B锁
System.out.println(Thread.currentThread().getName() + "拿到A锁和B锁");
}
}
};
Thread t1 = new Thread(r1, "线程1");
Thread t2 = new Thread(r2, "线程2");
t1.start();
t2.start();
上述代码中,两个线程分别试图获取锁"A"和锁"B",由于获取锁的顺序不同,可能会导致死锁的发生。
4. 如何避免死锁?
4.1 加锁顺序
通过规定线程获取锁的顺序,可以避免死锁。以下是一个示例代码:
Runnable r1 = () -> {
synchronized ("A") {
// 拿到A锁
System.out.println(Thread.currentThread().getName() + "拿到A锁");
try {
// 等待并释放A的锁标记
"A".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("B") {
// 拿到A锁和B锁
System.out.println(Thread.currentThread().getName() + "拿到A锁和B锁");
}
}
};
Runnable r2 = () -> {
synchronized ("B") {
// 拿到B锁
System.out.println(Thread.currentThread().getName() + "拿到B锁");
synchronized ("A") {
// 拿到A锁和B锁
System.out.println(Thread.currentThread().getName() + "拿到A锁和B锁");
// 释放A的线程锁
"A".notify();
}
}
};
Thread t1 = new Thread(r1, "线程1");
Thread t2 = new Thread(r2, "线程2");
t1.start();
t2.start();
4.2 加锁时限
在线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁。这需要结合tryLock
方法使用,超时时间可以由业务逻辑决定。
5. 总结
线程死锁是多线程编程中常见的问题,需要仔细设计程序结构来避免。通过规定加锁顺序和加锁时限等技术手段,可以有效预防和解决线程死锁问题。
正文到此结束
- 本文标签: Java 面试题
- 本文链接: https://www.jietongc.com/article/66
- 版权声明: 本文由大熊科技原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权