浅谈死锁
死锁是一种特定的程序状态,在实体之间,由于循环依赖导致彼此一直处于等待之中,没有任何个体可以继续前进。死锁不仅仅是在线程之间会发生,存在资源独占的进程之间同样也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。
下面是一个死锁的例子
/**
* create by jingchao on 16:33
*/
public class DeadLock extends Thread {
private String LockA;
private String LockB;
public DeadLock( String name,String lockA, String lockB) {
super(name);
LockA = lockA;
LockB = lockB;
}
public void run() {
synchronized (LockA){
System.out.println(this.getName()+"持有了"+LockA);
try {
Thread.sleep(1000);
synchronized (LockB){ System.out.println(this.getName()+"持有了"+LockB);
}
} catch (InterruptedException e) {
// do nothing
}
}
}
public static void main(String[] args) throws InterruptedException {
String LockA = "LockA";
String LocKB = "LockB";
DeadLock deadLock1 = new DeadLock("Thread1",LockA,LocKB);
DeadLock deadLock2 = new DeadLock("Thread2",LocKB,LockA);
deadLock1.start();
deadLock2.start();
deadLock1.join();
deadLock2.join();
}
}
可以看到此时两个线程相互等待对方所持有的锁,而发生了死锁状态。下面我们Dump Thread一下,看看线程的状态如何。
可以从图中看到,Thread1此时的状态为BLocked,Thread2也为Blocked,Thread2,等待的锁是<0x00000000d6cb9908>,而此时Thread1正持有该对象的锁,Thread1阻塞的原因是为了等待获取<0x00000000d6cb9940>,而该锁正在被Thread2所持有,故此,我们可以发现,Thread1和Thread2相互持有了对方所需要的锁,而发生了等待,形成了死锁。
如何在编程中尽量预防死锁呢?
首先,我们来总结一下前面例子中死锁的产生包含哪些基本元素。基本上死锁的发生是因为:
- 互斥条件,类似 Java 中 Monitor 都是独占的,要么是我用,要么是你用。
- 互斥条件是长期持有的,在使用结束之前,自己不会释放,也不能被其他线程抢占。
- 循环依赖关系,两个或者多个个体之间出现了锁的链条环。
如何避免死锁?
- 尽量避免使用多个锁,并且只有需要时才持有锁,嵌套的 synchronized 或者 lock 非常容易出问题。
- 如果必须使用多个锁,尽量设计好锁的获取顺序,这个说起来简单,做起来可不容易,你可以参看著名的银行家算法。
- 使用带超时的方法,为程序带来更多可控性。类似Object.wait(…)或者 CountDownLatch.await(…),都支持所谓的 timed_wait,我们完全可以就不假定该锁一定会获得,指定超时时间,并为无法得到锁时准备退出逻辑。