什么是死锁
死锁是一种特殊的程序状态,简单来说就是两个或多个线程之间循环依赖,互相持有对方需要的锁,导致线程无限期地处于阻塞状态。
下面通过一段代码来进一步了解一下死锁:
public class DeadlockTest {
private static String A = "对象A";
private static String B = "对象B";
public static void main(String[] args) {
new DeadlockTest().deadLock();
}
public void deadLock() {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (A) {
System.out.println("线程1锁住了对象A");
Thread.sleep(1000); //此处等待是为了给线程2机会锁住对象B
synchronized (B) {
System.out.println("线程1锁住了对象B");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (B) {
System.out.println("线程2锁住了对象B");
Thread.sleep(1000); //此处等待是给线程1机会锁住对象A
synchronized (A) {
System.out.println("线程2锁住了对象A");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
运行结果如下:
程序一直阻塞等待,无法正常结束。
因为通过上面的代码可以看到线程1在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入死锁循环。
死锁产生的条件
死锁产生有四个必要条件:
- 1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
- 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
当上述四个条件都成立的时候,便形成死锁。
解决方法
上面说了死锁产生的四个必要条件,但是此时如果打破上述任何一个条件,便可以让死锁消失。
所以下面几种方法便可以在一定程度上解决死锁问题:
- 1、让程序每次至多只能获得一个锁(破坏上面请求与保持的条件)。
- 2、设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。可以让系统为每类资源赋予一个编号,每个线程按照编号请求资源,然后释放则相反。
- 3、在线程满足一定条件时,释放掉已占有的资源。比如线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁。
- 4、死锁检测。即按照线程间获取锁的关系检测线程间是否发生了死锁,如果发生死锁就执行一定的策略,如终断线程或回滚操作等。还可以给这些线程设置优先级,发生死锁时让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。