概述
死锁是一个很严重的、必须要引起重视的问题。
尽管锁被持有的时间通常很短,但是作为商业产品的应用程序每天可能要执行数十亿次获取锁、释放锁的操作,只要在这数十亿次操作中只要有一次发生了错误,就可能导致程序中发生死锁,并且即使通过压力测试也不可能找出所有潜在的死锁。
死锁
当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞。
如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。
在数据库系统的设计中考虑了监测死锁以及从死锁中恢复,数据库如果监测到了一组事务发生了死锁时,将选择一个牺牲者并放弃这个事务。
Java虚拟机解决死锁问题方面并没有数据库这么强大,当一组Java线程发生死锁时,这两个线程就永远不能再使用了,并且由于两个线程分别持有了两个锁,那么这两段同步代码/代码块也无法再运行了(除非终止并重启应用)。
死锁示例
下面是一个简单的多线程死锁示例:
public class DeadLock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() throws Exception {
synchronized (left) {
Thread.sleep(2000);
synchronized (right) {
System.out.println("leftRight end!");
}
}
}
public void rightLeft() throws Exception {
synchronized (right) {
Thread.sleep(2000);
synchronized (left) {
System.out.println("rightLeft end!");
}
}
}
}
注意这里一定要有"Thread.sleep(2000)"让线程睡一觉,不然一个线程运行了,另一个线程还没有运行,先运行的线程很有可能就已经连续获得两个锁了。
写两个线程分别调用它们:
public class Thread0 extends Thread {
private DeadLock dl;
public Thread0(DeadLock dl) {
this.dl = dl;
}
public void run() {
try {
dl.leftRight();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Thread1 extends Thread {
private DeadLock dl;
public Thread1(DeadLock dl) {
this.dl = dl;
}
public void run() {
try {
dl.rightLeft();
} catch (Exception e) {
e.printStackTrace();
}
}
}
写个main函数执行
public static void main(String[] args) {
DeadLock dl = new DeadLock();
Thread0 t0 = new Thread0(dl);
Thread1 t1 = new Thread1(dl);
t0.start();
t1.start();
while (true) ;
}
执行main函数将发现没有任何输出,因为死锁了!
如何避免死锁
- 让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实
- 设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量
- 既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就好了。当然synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后变回返回一个失败信息
网友评论