死锁
死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
死锁发生的原因
死锁的发生是由于资源竞争导致的,导致死锁的原因如下:
- 系统资源不足,如果系统资源充足,死锁出现的可能性就很低。
- 进程(线程)运行推进的顺序不合适。
- 资源分配不当等。
死锁发生的条件
死锁的发生的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不可强行占有:进程(线程)已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程(线程)之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的例子
为了说明死锁,我们下面举一个例子来说明:
线程A持有资源A然后试图获取资源B,线程2持有资源B同时试图获取资源A,这样线程A和B都不能获取彼此想要的资源,导致整个程序不能向下推进。具体代码如下:
class DeadLockRunnable implements Runnable {
private String first;
private String second;
public DeadLockRunnable(String first, String second) {
this.first = first;
this.second = second;
}
@Override
public void run() {
synchronized (first) {
System.out.println(Thread.currentThread().getName() + " obtain: " + first);
try {
Thread.sleep(100);
synchronized (second) {
System.out.println(Thread.currentThread().getName() + " obtain: " + second);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class DeadLock {
public static void main(String[] args) throws InterruptedException {
String lockA = "lockA";
String lockB = "LockB";
Thread t1 = new Thread(new DeadLockRunnable(lockA, lockB));
Thread t2 = new Thread(new DeadLockRunnable(lockB, lockA));
t1.start();
t2.start();
t1.join();
t2.join();
}
}
运行结果:
从结果中可以看出,两个线程都不能再次获得想要的资源,程序一直处于“停滞”状态。
死锁的定位与预防
那么出现这种情况怎么定位呢?
- 通过jdk提供的命令
我们可以使用jstack或者类似Jconsole等图形化工具,查看线程的状态。上面程序jstack输出如下:
通过输出我们看到Thread-1和Thread-0都处于BLOCKED状态。至此我们基本可以知道程序发生了死锁。
- 分析CPU占用情况
根据死锁的定义,我们可以得出死锁一定会导致有线程处于”饥饿“状态而一直占用CPU时间片。因此可以通过排查出使用CPU时间片最高的线程,再打出该线程的堆栈信息,最后排查代码找出死锁的原因。
死锁的预防
- 尽量避免使用多个锁,并且只有需要时才持有锁。
- 如果使用多个锁,一定要设计好锁的获取顺序。
- 使用带有超时的方法,为程序带来更多的可控性,比如指定获取锁的时间最多为5秒,超时就放弃。
- 通过一些代码静态检查工具发现可能存在的死锁问题,比如FindBugs。